SyntaxBomb - Indie Coders

Languages & Coding => BlitzMax / BlitzMax NG => MiniB3D => Topic started by: Krischan on January 02, 2020, 00:03:18

Title: OpenB3D PBR Shader Demo
Post by: Krischan on January 02, 2020, 00:03:18
First: Happy New Year! Second: reports about my withdrawal from Blitzbasic are strongly exaggerated, I've had a lot of other things to do in 2019 ;D But recently I have been working intensively with Blender 2.8, PBR, GLSL and Substance. The result is a small demo which I would like to share with you. I have created a small video and some screenshots, have fun with it.

https://www.youtube.com/watch?v=sIriApU2x_8

The shader already looks quite good but I'm sure I've made some mistakes. First, the spotlight effect doesn't look right. I've played around a lot with it but couldn't make it any better. Second I'm sure there is a problem with the Parallax Occlusion mapping as it is only barely noticeable. Perhaps there is a problem with the Tangent/Worldspace coordinates.

If you want to take a look at the source by yourself, you can download it here with all textures and precompiled executables to run the demos in 1920x1080 windows:
PBR_ShaderDemo.zip (http://www.christianhart.de/bmax/PBR/PBR_ShaderDemo.zip) (20MB)

I would be very happy about any improvements and optimizations. ;) Oh, and Mark if you want you can use this or a simplified version of it in the OpenB3D demos.
Title: Re: OpenB3D PBR Shader Demo
Post by: Naughty Alien on January 02, 2020, 01:49:38
very very nice man..
Title: Re: OpenB3D PBR Shader Demo
Post by: Derron on January 02, 2020, 09:38:58
Are the bugs you mention what is visible at 1:15 of the video (and around 3:00 too) ?

Looking good so far - albeit I think bricks in old castles won't be so shiny new - or glossy from being so wet. They are worn out / old - almost breaking apart into the material they were made of. Exception is "natural" stone - which would be "rough" nonetheless - and only shiny in the metal or quartz vains they have built in.

Maybe having "rocks" (for nice bump/normal - and maybe tesselation presentation) and metal doors would be better to show stuff - might look "more realistic" then.
Or create some "water puddles" for reflection.

Also interesting to look (PBR wise) is ICE - so something with a different IOR (frozen glass ... ice ...) or stuff like "glowing iron" (so a fireplace of a smith with some iron glowing in).

As you have dynamic lights - maybe you could fake some fire (particles + some emitting and slightly moving/wobbling objects - or nonfaked, emissive particles).


Maybe this gave you some ideas for 2020 :)


PS: Blender 2.82 (or 81) help with their denoiser - except you are using Eevee already because it might look more similar to your engine than a rendering not using "faked" effects like Eevee does.
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on January 02, 2020, 13:48:24
> Are the bugs you mention what is visible at 1:15 of the video (and around 3:00 too)

Yes, that comes from the Parallax Occlusion function when you get too close to a wall - then the UVs are warped too much at very close distances. I think the function itself works, it must be something with the coordinate system.

I know that the bricks look artificial, it is very hard to find free suitable textures and I've just started working with Substance Designer, which is a very complex tool, so that's the best free texture I've found so far. I've started with my own textures from my game project and used the great tool Materialize (http://boundingboxsoftware.com/materialize/) to convert them to PBR and the results look quite good, but B2M3 (https://store.steampowered.com/app/325910/Substance_B2M3/) from Substance and Substance Designer (https://store.steampowered.com/app/1194120/Substance_Designer_2020/) produce much better results (they are currently on sale in Steam if you don't like the subscription Adobe introduced). I think it is better to create the texture from the scratch instead of converting a photo, but it takes a lot of time.

The metal demo is just an extreme example how different the reflections become with a metallic texture still using the same shader. My next goal ist to include everything into my RPG game project.

And I'd be happy if somebody could review the shader code. It was a hard piece of work to put it all together and I still don't understand some details in it.
Title: Re: OpenB3D PBR Shader Demo
Post by: iWasAdam on January 02, 2020, 15:22:01
be aware that Substance (designer and the rest) have been bought by Adobe and are no longer free. The current status of them is unsure as to whether Adobe will can them completely (absorbing the tech into photoshop, etc) or offer them as part of their monthly subscriptions.  :(
They will be discontinued from Steam at some stage - probably not too far in the future.

Very nice looking - add some shadows and you've got a winner :)
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on January 02, 2020, 15:57:48
Regarding Substance: I know. The current steam version gets Updates until January 2021 and I don't need the source textures, which are not included if you go Steam. BUT: I've synchronized my Steam account with Substance and got separate license keys from them for each of their products I've bought, so I can install them without Steam. Let's see what will happen in one year.

Shadows: I tried to add the OpenB3D shadows but first it looks weird and the demo crashes soon. Something must be colliding with my shader as the shadows work without the shader applied and vice versa. Perhaps Mark can take a look at it.
Title: Re: OpenB3D PBR Shader Demo
Post by: Derron on January 02, 2020, 16:02:59
When were Substance products free?

For PBR there are some node setup tutorials to create PBR from single textures in Blender. So it might be useable to generate textures from it.
I remember the time when people draw in their scratches and cavity maps by hand instead of one-clicking in SP.


Bye
Ron
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on January 02, 2020, 16:16:58
I think in a few years, Blender will become the main Open Source competitor for Substance. But not yet. After Adobe acquired Allegorithmic there were a lot of discussions. You can even discuss if it is worth to buy the Steam version(s) or go for subscription already (Steam: 233,61€ vs. 195,60€ subscription). And without a sale, Steam is even more expensive. But I don't like subscriptions and Steam is the only way to get a real license key at the moment, but with a one-year limited update plan.

I played a lot with Filter Forge and other tools but they are slow and the results are not so stunning like in Substance Designer. And I don't want to draw my textures only in Photoshop when it comes to PBR which needs the 3rd dimenson.
Title: Re: OpenB3D PBR Shader Demo
Post by: Derron on January 02, 2020, 17:53:19
There are no real counterparts for Substance Painter at the moment - some say one should use ArmorPaint:
https://armorpaint.org (downloads there are 16$)
https://github.com/armory3d/armorpaint (build it on your own or use one of the older releases there - for free)

If you really enjoy creating your own materials then ArmorPaint and its node based approach might be a joy for you. If you prefer "premade" materials with some knobs to adjust  then go with Substance Painter (I know you talk about Substance Designer). Both allow layer/mask based workflows.

I wished there was an FOSS alternative to SP - with an almost identical workflow/layout. But it won't happen as it does not happen for Photoshop (best chances for now is "Krita" - has more attention than Gimp).


bye
Ron
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on January 02, 2020, 17:58:28
For Photoshop: ever tried https://www.photopea.com (https://www.photopea.com)? Not exactly Photoshop, but a well done clone for free.
Title: Re: OpenB3D PBR Shader Demo
Post by: Derron on January 02, 2020, 18:06:28
Yes I tried that. It misses most of the vector part (it has shapes but vector manipulation is missing). Also disliking the "online" thing. Pressure sensitivity is something I did not get working with it too - albeit there seems to be support for it.

Will have to stay with my stone old PS for now ... via wine it sucks but has working pressure sensitivity. If I only do "mouse work" I prefer to run it in an old XP-VM as it does not have some window-manager-problems like in wine (tool panels behind main window) - or odd crashes.


bye
Ron
Title: Re: OpenB3D PBR Shader Demo
Post by: Qube on January 03, 2020, 09:34:28
Wow! now that looks impressive. Very nice work indeed :)
Title: Re: OpenB3D PBR Shader Demo
Post by: Steve Elliott on January 03, 2020, 10:01:14
Some very nice work here, and what could be the beginnings of a very atmospheric dungeon-like game.
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on January 05, 2020, 09:11:29
Quote from: Steve Elliott on January 03, 2020, 10:01:14
Some very nice work here, and what could be the beginnings of a very atmospheric dungeon-like game.

I'm working on that. ;D Beside that: are there no GLSL shader experts here to take a look at my problems? I hardly dare to post the problem at khronos.org community as we're using a "dead" language and the legacy version 1.30 / OpenGL 2.0 pipeline in OpenB3D and the guys will probably laugh at me. ::)
Title: Re: OpenB3D PBR Shader Demo
Post by: Naughty Alien on January 05, 2020, 09:14:14
I think Gabor could give you a hand, but he is probably busy elsewhere..
Title: Re: OpenB3D PBR Shader Demo
Post by: iWasAdam on January 05, 2020, 10:58:31
Quoteare there no GLSL shader experts here to take a look at my problems?
post the issues and we can always look at it and see what can be done :)
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on January 05, 2020, 14:59:51
Ok, the issues more detailed: 8)

Screenshot A+B: enabling the Parallax Occlusion Mapping (Key 5), the effect is only barely noticeable and when you get close to walls there is a massive distortion of the UV coordinates. But I don't know why. I've read a lot about POM and an important issue seems that your UV coordinates must be in the "right" space (tangent?), but I don't understand the examples I've found.

And in general, the spotlight looks strange, too flat compared to the fireball. I'd expect more reflection/depth "feedback" from the light there. And that the spotlight reflections change when I move the camera around. It looks like a pointlight which is only moving along the X/Z axis when I walk around but not with the camera rotation. You know what I mean?

Vertex  Shader:
Code (glsl) Select
#version 130

#define NUM_LIGHTS 5

// ----------------------------------------------------------------------------
// Constants and Structs
// ----------------------------------------------------------------------------

struct FloatArray {
float Float;
};


// ----------------------------------------------------------------------------
// Output values to the Fragment Shader
// ----------------------------------------------------------------------------

out vec2 Vertex_UV;
out vec3 Vertex_Normal;
out vec4 Vertex_Position;
out vec3 Vertex_Eyevector;

out vec3 Vertex_LightDir[NUM_LIGHTS];
out vec3 Vertex_LightColor[NUM_LIGHTS];
out float Vertex_LightRange[NUM_LIGHTS];

out vec3 Vertex_AmbientColor;


// ----------------------------------------------------------------------------
// Attributes from the main program
// ----------------------------------------------------------------------------

uniform vec2 texscale;
uniform vec2 texoffset;
uniform float ambFactor;
uniform FloatArray lightradius[NUM_LIGHTS];


// ----------------------------------------------------------------------------
// Main Vertex Shader
// ----------------------------------------------------------------------------

void main()
{
// Normal, Fragment and UV coordinates
Vertex_Normal = normalize(gl_NormalMatrix * gl_Normal);
Vertex_Position = gl_ModelViewMatrix * gl_Vertex;
Vertex_UV = (gl_MultiTexCoord0.xy * texscale) + texoffset;

// Eye vector
vec4 ecPosition = gl_ModelViewMatrix * gl_Vertex;
vec3 ecPosition3 = (vec3(ecPosition)) / ecPosition.w;
Vertex_Eyevector = -normalize(ecPosition3);

// Light properties
for (int i = 0; i < NUM_LIGHTS; ++i)
{
Vertex_LightDir[i] = gl_LightSource[i].position.xyz - Vertex_Position.xyz;
Vertex_LightColor[i] = gl_LightSource[i].diffuse.rgb;
Vertex_LightRange[i] = lightradius[i].Float * lightradius[i].Float;
}

// Ambient color
Vertex_AmbientColor = gl_LightModel.ambient.rgb * ambFactor;

gl_Position = ftransform();
}


Fragement Shader:
Code (glsl) Select
#version 130

#define NUM_LIGHTS 5

// ----------------------------------------------------------------------------
// Constants and Structs
// ----------------------------------------------------------------------------

struct FloatArray {
    float Float;
};

const float PI = 3.14159265359;
const vec2 PMheight = vec2(0.04,-0.02);


// ----------------------------------------------------------------------------
// Input Textures from the main program
// ----------------------------------------------------------------------------

uniform sampler2D albedoMap;
uniform sampler2D normalMap;
uniform sampler2D roughnessMap;
uniform sampler2D metallicMap;
uniform sampler2D heightMap;
uniform sampler2D aoMap;
uniform sampler2D emissionMap;


// ----------------------------------------------------------------------------
// Attributes from the Vertex Shader
// ----------------------------------------------------------------------------

in vec2 Vertex_UV;
in vec3 Vertex_Normal;
in vec4 Vertex_Position;
in vec3 Vertex_Eyevector;
in vec3 Vertex_LightDir[NUM_LIGHTS];
in vec3 Vertex_LightColor[NUM_LIGHTS];
in float Vertex_LightRange[NUM_LIGHTS];
in vec3 Vertex_AmbientColor;

out vec4 FragColor;


// ----------------------------------------------------------------------------
// Attributes from the main program
// ----------------------------------------------------------------------------

uniform float levelscale;
uniform float gamma;

uniform float fogStart;
uniform float fogRange;
uniform float fogDensity;
uniform vec3 fogColor;

uniform float AttA;
uniform float AttB;
uniform float flicker;
uniform FloatArray lightradius[NUM_LIGHTS];

uniform int isMetal;

// ----------------------------------------------------------------------------

uniform int flagDM;
uniform int flagFG;
uniform int flagPB;
uniform int flagTM;
uniform int flagTL;

// ----------------------------------------------------------------------------

uniform int texAL;
uniform int texNM;
uniform int texRO;
uniform int texME;
uniform int texHM;
uniform int texAO;
uniform int texEM;


// ----------------------------------------------------------------------------
// Normal functions
// ----------------------------------------------------------------------------

mat3 cotangent_frame(vec3 N, vec3 p, vec2 uv)
{
vec3 dp1 = dFdx(p);
vec3 dp2 = dFdy(p);
vec2 duv1 = dFdx(uv);
vec2 duv2 = dFdy(uv);

vec3 dp2perp = cross(dp2, N);
vec3 dp1perp = cross(N, dp1);
vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;

float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
return mat3(T * invmax, B * invmax, N);
}

// ----------------------------------------------------------------------------

vec3 perturb_normal(vec3 N, vec3 V, vec3 map, vec2 texcoord)
{
map = map * 255.0 / 127.0 - 128.0 / 127.0;
mat3 TBN = cotangent_frame(N, -V, texcoord);
return normalize(TBN * map);
}


// ----------------------------------------------------------------------------
// Tonemapping functions
// ----------------------------------------------------------------------------

vec3 ToneMapFilmic(vec3 color)
{
vec4 vh = vec4(color, gamma);
vec4 va = 1.425 * vh + 0.05;
vec4 vf = (vh * va + 0.004) / (vh * (va + 0.55) + 0.0491) - 0.0821;
return vf.rgb / vf.www;
}

// ----------------------------------------------------------------------------

vec3 ToneMapSimple(vec3 color)
{
return pow(color / (color + vec3(1.0)), vec3(1.0 / gamma));

}

// ----------------------------------------------------------------------------

vec3 ToneMapExposure(vec3 color)
{
color = exp(-1.0 / ( 2.72 * color + 0.15 ));
color = pow(color, vec3(1. / gamma));
return color;
}

// ----------------------------------------------------------------------------

vec3 ToneMapPBR(vec3 color)
{
// HDR tonemapping
color = color / (color + vec3(1.0));
// gamma correct
color = pow(color, vec3(1.0 / gamma));

return color;
}

// ----------------------------------------------------------------------------

vec3 Uncharted(vec3 x)
{
float A = 0.15;
float B = 0.50;
float C = 0.10;
float D = 0.20;
float E = 0.02;
float F = 0.30;

return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
}

// ----------------------------------------------------------------------------

vec3 ToneMapUncharted(vec3 color)
{
color = Uncharted(color * 4.5) * (1.0 / Uncharted(vec3(11.2)));
color = pow(color, vec3(1.0 / gamma));
return color;

}

// ----------------------------------------------------------------------------

vec3 ToneMapSCurve(vec3 x)
{
//x = pow(x, vec3(1.0 / 2.2));

float a = 2.51f;
float b = 0.03f;
float c = 2.43f;
float d = 0.59f;
float e = 0.14f;
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
}


// ----------------------------------------------------------------------------
// PBR functions
// ----------------------------------------------------------------------------

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 NdotH2 = NdotH * NdotH;

float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;

return nom / denom;
}

// ----------------------------------------------------------------------------

float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r * r) / 8.0;

float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;

return nom / denom;
}

// ----------------------------------------------------------------------------

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 ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);

return ggx1 * ggx2;
}

// ----------------------------------------------------------------------------

vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
if(cosTheta > 1.0)
cosTheta = 1.0;
float p = pow(1.0 - cosTheta,5.0);
return F0 + (1.0 - F0) * p;
}


// ----------------------------------------------------------------------------
// Parallax Occlusion Mapping
// ----------------------------------------------------------------------------

vec2 ParallaxOcclusionMapping(vec2 texCoords, vec3 viewDir)
{
// number of depth layers
const vec2 heightScale = vec2(0.00625,-0.00625);
const float minLayers = 8;
const float maxLayers = 32;
float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));

// calculate the size of each layer
float layerDepth = 1.0 / numLayers;

// depth of current layer
float currentLayerDepth = 0.0;

// the amount to shift the texture coordinates per layer (from vector P)
vec2 P = viewDir.xy / viewDir.z * heightScale;
vec2 deltaTexCoords = P / numLayers;
 
// get initial values
vec2  currentTexCoords = texCoords;
float currentDepthMapValue = texture(heightMap, currentTexCoords).r;
     
while(currentLayerDepth < currentDepthMapValue)
{
// shift texture coordinates along direction of P
currentTexCoords -= deltaTexCoords;
// get depthmap value at current texture coordinates
currentDepthMapValue = texture(heightMap, currentTexCoords).r; 
// get depth of next layer
currentLayerDepth += layerDepth; 
}
   
// get texture coordinates before collision (reverse operations)
vec2 prevTexCoords = currentTexCoords + deltaTexCoords;

// get depth after and before collision for linear interpolation
float afterDepth = currentDepthMapValue - currentLayerDepth;
float beforeDepth = texture(heightMap, prevTexCoords).r - currentLayerDepth + layerDepth;

// interpolation of texture coordinates
float weight = afterDepth / (afterDepth - beforeDepth);
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);

return finalTexCoords;
}


// ----------------------------------------------------------------------------
// Light functions
// ----------------------------------------------------------------------------

float LinearizeDepth(float depth) // Note that this ranges from [0,1] instead of up to 'far plane distance' since we divide by 'far'
{
float near = 0.1;
float far = fogRange*1.0/levelscale;
float z = depth * 2.0 - 1.0; // Back to NDC
return (2.0 * near) / (far + near - z * (far - near));
}

// ----------------------------------------------------------------------------

float CalcAtt(float distance)
{
return 1.0 / (1.0 + AttA * distance + AttB * distance * distance);
}

// ----------------------------------------------------------------------------

float spotlight(int i, float attenuation, vec3 L, float intensity)
{
float clampedCosine = max(0.0, 1.0 * dot(-L, gl_LightSource[i].spotDirection));
attenuation = attenuation * pow(clampedCosine, gl_LightSource[i].spotExponent*intensity);

return attenuation;
}


// ----------------------------------------------------------------------------
// Main Fragment Shader
// ----------------------------------------------------------------------------

void main()
{
vec2 uv = Vertex_UV;

// Parallax Mapping
if(texHM > 0){uv = ParallaxOcclusionMapping(uv, normalize(Vertex_Eyevector.xyz));}

// 1. Albedo Texture
vec4 albedo = vec4(0.5, 0.5, 0.5, 1.0);
if(texAL > 0){albedo = texture(albedoMap, uv);}

// 2. Normalmap Texture
vec3 nrm = Vertex_Normal;
if(texNM > 0){nrm = texture(normalMap, uv).rgb;}

// 3. Roughness Texture
float roughness = 0.7;
if(texRO > 0){roughness = texture(roughnessMap, uv).r;}

// 4. Metallic Texture
float metallic = 0.2;
if(texME > 0){metallic = texture(metallicMap, uv).r;}

// 5. Ambient Occlusion Texture
float ao = 1.0;
if(texAO > 0){ao = texture(aoMap, uv).r;}

// 6. Emissive Texture
vec3 emission = vec3(0.0);
if(texEM > 0){emission = texture(emissionMap, uv).rgb * (1.0 + (flicker / 2.0)) * 4.0;}

vec3 Lo = vec3(0.0);

// ambient and emission lighting
vec3 color = (emission + Vertex_AmbientColor) * albedo.rgb;
vec3 r = vec3(0.0);

// PBR active
if(flagPB > 0)
{

vec3 N = Vertex_Normal.xyz;
vec3 V = normalize(Vertex_Eyevector.xyz);
vec3 PN = perturb_normal(N, V, nrm, uv);

// calculate reflectance at normal incidence; if dia-electric (like plastic) use F0
// of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)   
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo.rgb, metallic);
if(isMetal > 0){F0 = albedo.rgb;}

for(int i = 0; i < NUM_LIGHTS; ++i)
{
// calculate per-light radiance
vec3 L = normalize(Vertex_LightDir[i]);
vec3 X = normalize(gl_LightSource[0].spotDirection.xyz);


vec3 H = normalize(V + L);
float distance = length(Vertex_LightDir[i]) * 1.0 / levelscale;
float attenuation = CalcAtt(distance) * Vertex_LightRange[i];

// first light = player spotlight
if(i == 0 && flagTL == 1)
{
attenuation = spotlight(0, attenuation, L, 1.0);
attenuation = pow(attenuation / (attenuation + 1.0 + flicker), 1.0 / gamma) * (2.0 - 2.0 / Vertex_LightRange[i]);
}

if(i == 0 && flagTL == 0)
{
attenuation = 1.0 / (1.0 + 0.0 * distance + 0.5 * distance * distance);
}

// light color (scaled)
vec3 radiance = Vertex_LightColor[i] * attenuation;

// Cook-Torrance BRDF
float NDF = DistributionGGX(PN, H, roughness);
float G = GeometrySmith(PN, V, L, roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
           
vec3 nominator = NDF * G * F;
float denominator = 2 * max(dot(PN, V), 0.0) * max(dot(PN, L), 0.0) + 0.001; // 0.001 to prevent divide by zero.
vec3 specular = nominator / denominator;
       
// kS is equal to Fresnel
vec3 kS = F;
// for energy conservation, the diffuse and specular light can't
// be above 1.0 (unless the surface emits light); to preserve this
// relationship the diffuse component (kD) should equal 1.0 - kS.
vec3 kD = clamp(vec3(1.0) - kS, 0.0, 1.0);
// multiply kD by the inverse metalness such that only non-metals
// have diffuse lighting, or a linear blend if partly metal (pure metals
// have no diffuse light).
kD *= 1.0 - metallic;


// non-player light: check backface lighting
float NdotL = 1.0;
if(i > 0)
{
float NdotL = max(dot(PN, L), 0.0);
if(NdotL > 0.0)
{
Lo += (kD * albedo.rgb / PI + specular) * radiance * NdotL * attenuation;
r += attenuation;

}
}
// player light: simpler light equotation
else
{
Lo += Lo += (albedo.rgb / PI + specular) * radiance * attenuation;
r += attenuation;
}

}
}

// PBR off
else
{
for(int i = 0; i < NUM_LIGHTS; ++i)
{
vec3 L = normalize(Vertex_LightDir[i]);
vec3 N = Vertex_Normal.xyz;
float NdotL = max(dot(N, L), 0.0);

float distance = length(Vertex_LightDir[i]) * 1.0 / levelscale;
float attenuation = CalcAtt(distance) * Vertex_LightRange[i];

// first light = player spotlight
if(i == 0 && flagTL == 1)
{
attenuation = spotlight(0, attenuation, L, 2.0);
attenuation = pow(attenuation / (attenuation + 1.0 + flicker), 1.0 / gamma) * (2.0 - 2.0 / Vertex_LightRange[i]);
}

if(i == 0 && flagTL == 0)
{
attenuation = 1.0 / (1.0 + 0.0 * distance + 0.5 * distance * distance);
}

if(NdotL > 0.0)
{
Lo += albedo.rgb * Vertex_LightColor[i] * attenuation * NdotL;
r += attenuation;
}

}
}

// put it all together
color += (color + Lo) * ao;

// Tonemapping
if(flagTM == 1){color = ToneMapFilmic(color);}
if(flagTM == 2){color = ToneMapSimple(color);}
if(flagTM == 3){color = ToneMapExposure(color);}
if(flagTM == 4){color = ToneMapPBR(color);}
if(flagTM == 5){color = ToneMapUncharted(color);}
if(flagTM == 6){color = ToneMapSCurve(color);}

// fog
if(flagFG > 0)
{
float plane;
plane = length(Vertex_Position);   // range-based (radial), flat would be plane = abs(position.z)
float fogFactor = clamp((fogRange - plane) / (fogRange - fogStart), 0.0, 1.0);

// distant lights shine through the fog a little bit
vec3 fogFactorX = max(clamp(r, 0.0, 0.5), vec3(fogFactor)) * (1.0 - fogDensity);

color = (fogColor * (1.0 - fogFactorX) + (color.rgb * fogFactorX));

}

// simple depthmap
if(flagDM > 0)
{
float depth = 1.0 - LinearizeDepth(gl_FragCoord.z);
color.rgb = vec3(depth);
}

FragColor = vec4(color, albedo.a);

}
Title: Re: OpenB3D PBR Shader Demo
Post by: angros47 on February 09, 2023, 20:16:58
It states that parallax occlusion mapping doesn't work correctly.

In the file shader.frag, I tried replacing the line:

// Parallax Mapping
if(texHM > 0){uv = ParallaxOcclusionMapping(uv, normalize(Vertex_Eyevector.xyz));}


With:

// Parallax Mapping
if(texHM > 0){
vec3 N = Vertex_Normal.xyz;
vec3 V = Vertex_Eyevector.xyz;
mat3 TBN = cotangent_frame(N, V, uv);
uv = ParallaxOcclusionMapping(uv, TBN *V);
}


Anyone wants to test if it works better?
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on February 10, 2023, 11:13:00
Well, the post is three years old and I'm currently not coding in Blitzmax, but last year I've found a solution, although I have no demo ready to show, it's not complete, quite complex, but working so far (see screenshot).

The trick is to calculate the TBN matrix from the normalized view vector first, apply the new UVs to all PBR textures excluding the Normal Map and to perturbate the Normals in the PBR shader. I hope you can still follow me? ;D Anyway, here are some code fragments:

In the Vertex Shader, you need - beside the UVs and the Normal - the "view vector" which must be passed to the fragment shader:


out vec2 Vertex_UV;
out vec3 Vertex_Normal;
out vec3 viewvector;

Vertex_UV = gl_MultiTexCoord0.xy;
Vertex_Normal = normalize(gl_NormalMatrix * gl_Normal);
viewposition = gl_ModelViewMatrix * gl_Vertex;
viewvector = -viewposition.xyz;


And in the Fragment shader (only the relevant parts included here with Simple Parallax Mapping only, you can also use Steep Parallax Mapping or any other method here)


const float POscale = 1.0/32;
const float POsteps = 32;

in vec2 Vertex_UV;
in vec3 Vertex_Normal;
in vec3 viewvector;

mat3 cotangent_frame(vec3 N, vec3 p, vec2 uv)
{
vec3 dp1 = dFdx(p);
vec3 dp2 = dFdy(p);
vec2 duv1 = dFdx(uv);
vec2 duv = dFdy(uv);

vec3 dp2perp = cross(dp2, N);
vec3 dp1perp = cross(N, dp1);
vec3 T = dp2perp * duv1.x + dp1perp * duv.x;
vec3 B = dp2perp * duv1.y + dp1perp * duv.y;

float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
return mat3(T * invmax, B * invmax, N);
}

vec3 perturb_normal(vec3 N, vec3 V, vec3 map, vec2 texcoord)
{
map = (map * 255.0 / 127.0 - 128.0 / 127.0)*normalMulti;

mat3 TBN = cotangent_frame(N, -V, texcoord);
return normalize(TBN * map);
}

// Simple Parallax Mapping
vec2 parallax(vec2 uv, vec3 view)
{
float height_scale = POscale*0.5;
float height = texture(heightMap, uv).r;
view.z = -view.z;
vec2 p = view.xy / view.z * (height * height_scale);
return uv - p;
}

vec2 uv = Vertex_UV;
vec3 VN = normalize(Vertex_Normal);
vec3 VV = normalize(viewvector);
mat3 TBN = cotangent_frame(VN, -VV, uv.st);
uv = parallax(uv, normalize(-VV * TBN));


This uv variable is then used at the PBR textures like albedo, ao, roughness and metalness. The Normals must then be "perturbated" with the following method (important: must be performed AFTER the code above as we need the PO UVs there) to get "different" UVs for the PBR lighting/specularity:


vec3 nrm = nrm = texture(normalMap, uv).rgb;
// Perturbated Normals
vec3 N = Vertex_Normal.xyz;
vec3 V = normalize(viewvector.xyz);
vec3 PN;
vec3 VN = normalize(N);
vec3 VV = normalize(viewvector);
mat3 TBN = cotangent_frame(VN, -VV, uv.st);
PN = perturb_normal(VN * TBN, VV, nrm, uv);


The PN variable is later used in the PBR lighting code for the Specularity/nominator/denominator:


// Specularity ----------------------------------------------------
vec3 nominator = NDF * G * F;
float denominator = 4.0 * max(dot(PN, V), 0.0) * max(dot(PN, L), 0.0);
vec3 specular = (nominator / max(denominator, 0.0001));


Maybe I'll finish a complete and simple demo later this year but I'm not really into coding atm as I spent most of my time in the Decentraland metaverse now. You can find me there as "METATIGER" if you want to take a look, it's free and very cool :D

https://play.decentraland.org (https://play.decentraland.org)
Title: Re: OpenB3D PBR Shader Demo
Post by: angros47 on February 10, 2023, 11:17:12
I have also a question: what is the license of your shader? Because I was considering to include it in OpenB3D Plus, in case you have no objections. Can you post the complete version of the shader?
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on February 10, 2023, 11:34:23
I have not thought about a license yet, maybe a CC Attribution? Feel free to use it like you want. Here is the complete shader but it is very messy at the moment. I think it will confuse you more than it helps until I clean it up and simplify it. Also the lighting is faulty as multiple light do not work correct. Needs to be totally rewritten as far as I remember.

Anyway I've attached a GIF animation of three screenshots showing all three states: None, Simple and Steep Parallax Occlusion mapping (with a very exaggerated POscale of 1.0/8).
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on February 10, 2023, 11:40:28
You may also need a test texture, you can use these if you want. It is the albedo map, the normal map and a combined texture for the PBR calculations: red=AO, green=roughness, blue = metallic and alpha = height.
Title: Re: OpenB3D PBR Shader Demo
Post by: angros47 on February 10, 2023, 12:59:53
Thank you!
Title: Re: OpenB3D PBR Shader Demo
Post by: markcwm on February 11, 2023, 16:24:27
Hi Krischan, I never really understood the concept of PBR shaders, does anyone know of a good tutorial explaining it? I'd still like to know.

Off-topic: there's a post about your cool BSP loader (https://www.blitzcoder.org/forum/topic.php?id=631#2) for Blitz3d if you're interested?

Your projects were always very impressive Krischan, thanks for all your releases.

I'm sure you would have had better luck with Unity3D or Godot.
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on February 11, 2023, 16:37:05
Hey mark,

the PBR concept is quite easy - it's all about light, reflection and "energy conservation". I really should write a very small demo to show it, and it looks also impressive even in Blitzmax and OpenB3D. For PBR, I've learned a lot from this page: https://learnopengl.com/PBR/Theory (https://learnopengl.com/PBR/Theory) Shadertoy is also a good source for quality shaders, functions and concepts. But most coders there are waaaaay ahead of me  8)

I'm not working with Blitz3D anymore since years LOL - almost forgot this demo. I'm still working with BSPs but never managed to get the Patches running, only the brushes - one of the reasons why I've stopped working on my project at the moment. I'm also unsure which system I should use - I'm more addicted with Unreal Engine as they have a great texture pool and Quixel Mixer is also great creating new (PBR) textures.

And since I've discovered the metaverse I've almost abandoned coding and focused more on creating 3D models there.
Title: Re: OpenB3D PBR Shader Demo
Post by: markcwm on February 13, 2023, 22:15:32
Hey Krischan,

thanks for the summary and link, I'll be sure to read more when I have the time.

Sorry, I don't understand BSP levels much. Did your loader/s always only read Quake 3 BSP files? Going by this BSP info (http://www.mralligator.com/q3/) patches sound like hard math. Here is Paul Bourke's Bezier Surface code (http://paulbourke.net/geometry/bezier/).

I always thought it would be cool to code 3D Bezier Curves for use in NPC movement, but this is different to Bezier Surfaces. Ah well, maybe one day.
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on February 13, 2023, 22:36:06
Yes, only Q3 BSPs as I've been most familar with RTCW/ET editing only. I've got most of the information about BSP from the attached file(s), maybe you can find some useful information there. I'd really appreciate if somebody could provide some bmax code how to render these bezier patches from a BSP file.

The brush geometry however is quite easy, I've also written a 2D to 3D map tool for my game project where you could click+build a map in 2D and it exports the source .map file for the Radiant editor in 3D which can then be compiled there into a BSP file, very neat. But also without patch support, only brushes (but it supports prefabs).

And did you see the message I've sent you here in this board? I forgot to give it a subject title :p
Title: Re: OpenB3D PBR Shader Demo
Post by: angros47 on February 19, 2023, 23:35:40
A little demo of the new shader would help, I am struggling to pass it parmeters
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on March 02, 2023, 14:26:46
Finally I found some time to create a cool PBR shader demo using Blitzmax NG and OpenB3D. Have fun!

Download (10MB): ShaderExample.zip (https://www.christianhart.de/bmax/ShaderExample.zip)

ShaderExample.jpg ShaderExample_minimum.jpg



Blitzmax:
SuperStrict

Framework openb3d.B3dglgraphics

?bmxng
Import brl.RandomDefault
Import BRL.TimerDefault
?

Import maxgui.Drivers

' Screen
Global SCREEN_WIDTH:Int = 1920
Global SCREEN_HEIGHT:Int = 1080
Global FPS:Int = 60

' Scale
Global levelscale:Float = 128.0

' Texture
Global texformat:String = "tga"
Global texture:String = "gold"  ' <<< set texture basename here <<<
Global modeltex:String = "textures/" + texture
Global texturescale:Float[] = [6.0, -6.0, 0.0, 0.0]

' Shader
Global shaderprefix:String = "pbr"
Global VERTEXSHADER_COMPILED:TShaderObject
Global FRAGMENTSHADER_COMPILED:TShaderObject

' Lights
Global OPENGLIGHTS:TLight[8]
Global OPENGLIGHTS_RANGE:Float[8]

' MaxGUI
Global MAXGUI_WINDOW:TGadget = CreateWindow("Shaderdemo (MaxGUI/OpenB3D)", ClientWidth(Desktop()) / 2 - ((SCREEN_WIDTH) / 2), ClientHeight(Desktop()) / 2 - ((SCREEN_HEIGHT) / 2), SCREEN_WIDTH, SCREEN_HEIGHT, Null, Null)
Global MAXGUI_CANVAS:TGadget = CreateCanvas(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, MAXGUI_WINDOW, 0)
SetGadgetLayout(MAXGUI_CANVAS, 1, 1, 1, 1)
ActivateGadget(MAXGUI_CANVAS)
SetGraphics CanvasGraphics(MAXGUI_CANVAS)
Graphics3D(SCREEN_WIDTH, SCREEN_HEIGHT, 0, 2, FPS, -1, True)
EnablePolledInput()

' Masterpivot
Global pivot:TPivot = CreatePivot()
PositionEntity pivot, 0, 0, levelscale * 3

Global model:TMesh = LoadAnimMesh("models/mat.b3d", pivot)
FitMesh model, -0.5, -0.5, -0.5, 1, 1, 1, 1
ScaleEntity model, levelscale, levelscale, levelscale

' Environmental Mapping Cubemap
Global ENVMAP:TTexture = LoadAnimTexture("fx/env.tga", 1 + 128, 256, 256, 0, 0)

' Light
AmbientLight 32, 32, 32

For Local i:Int = 0 To 7

    OPENGLIGHTS[i] = CreateLight(2)
    OPENGLIGHTS_RANGE[i] = 10.0
    LightColor OPENGLIGHTS[i], 255, 255, 255
    PositionEntity OPENGLIGHTS[i], (i - 3) * 2.0 * levelscale, Float(Sin(i * 45) * levelscale * 2), levelscale
   
Next

' Shader
InitShader()
Global modelshader:TShader = PrepareShader(VERTEXSHADER_COMPILED, FRAGMENTSHADER_COMPILED)
SetShaderTexture(modelshader, modeltex, texturescale)
ShadeEntity(model, modelshader)

' Camera
Global camera:TCamera = CreateCamera()
PositionEntity camera, 0, -levelscale * 0.05, levelscale * 1.75
CameraClsColor camera, 32, 32, 32
CameraRange camera, 0.1, levelscale * 4

CenterMouse()

While Not KeyDown(KEY_ESCAPE)

    RotateEntity pivot, (1.0 - MouseY() * 2.0 / SCREEN_HEIGHT) * 180, 0, 0
    RotateEntity model, 0, (MouseX() * 1.0 / SCREEN_WIDTH * 2.0) * 180 - 180, 0

    RenderWorld

    Flip
   
Wend

End

Function CenterMouse()

    Repeat
   
        MoveMouse Int(SCREEN_WIDTH / 2.0 + Rnd(-0.1, 0.1)), Int(SCREEN_HEIGHT / 2.0 + Rnd(-0.1, 0.1))
       
    Until (MouseX() = SCREEN_WIDTH / 2 And MouseY() = SCREEN_HEIGHT / 2)

End Function

Function InitShader()

    Global VERTEXSHADER_SOURCE:String
    Global FRAGMENTSHADER_SOURCE:String
    Global MAINSHADER:TShader

    Local PATH_SHADER:String = "shader/"
   
    ' load the shader source once
    Local s:TStream
    s = ReadFile(PATH_SHADER + shaderprefix + ".vert") ; VERTEXSHADER_SOURCE = ReadString(s, Int(FileSize(PATH_SHADER + shaderprefix + ".vert")))
    s = ReadFile(PATH_SHADER + shaderprefix + ".frag") ; FRAGMENTSHADER_SOURCE = ReadString(s, Int(FileSize(PATH_SHADER + shaderprefix + ".frag")))
    CloseStream s
   
    ' create the main shader object
    MAINSHADER = CreateShaderMaterial("")
   
    ' compile the vertex and fragment shader sources
    FRAGMENTSHADER_COMPILED = CreateFragShaderString(MAINSHADER, FRAGMENTSHADER_SOURCE)
    VERTEXSHADER_COMPILED = CreateVertShaderString(MAINSHADER, VERTEXSHADER_SOURCE)

End Function

Function PrepareShader:TShader(vobj:TShaderObject, fobj:TShaderObject)

    Local shader:TShader = CreateShaderMaterial("")
    AttachFragShader(shader, fobj)
    AttachVertShader(shader, vobj)
    linkshader(shader)
   
    Return shader

End Function

' ------------------------------------------------------------------------------------------------
' initialize a single shader with different textures but same settings
' ------------------------------------------------------------------------------------------------
Function SetShaderTexture(shader:TShader, tex:String, texscale:Float[])

    ' texture exist flags
    Local keyAL:Int, keyNM:Int, keyPR:Int, keyME:Int, keyEM:Int
    Local ismetal:Int = 0

    ' combine the texture filenames
    Local ALfile:String = tex + "." + texformat        ' Albedo/Basecolor
    Local NMfile:String = tex + "_NRM." + texformat    ' Normalmap
    Local PRfile:String = tex + "_PBR." + texformat    ' PBR (R: AO, G: Roughness, B: Displace)
    Local MEfile:String = tex + "_MET." + texformat    ' PBR (R: Metallic)
    Local EMfile:String = tex + "_EMI." + texformat    ' Emission
   
    ' check if the textures exist
    If(OpenFile(ALfile)) Then keyAL = 1 ; SetInteger(shader, "texAL", keyAL)
    If(OpenFile(NMfile)) Then keyNM = 1 ; SetInteger(shader, "texNM", keyNM)
    If(OpenFile(PRfile)) Then keyPR = 1 ; SetInteger(shader, "texPR", keyPR)
    If(OpenFile(MEfile)) Then keyME = 1 ; SetInteger(shader, "texME", keyME) ; ismetal = 1
    If(OpenFile(EMfile)) Then keyEM = 1 ; SetInteger(shader, "texEM", keyEM)
   
    ' if they exist, load and pass them to the shader, set shader flags
    If(keyAL) Then ShaderTexture(shader, LoadTexture(ALfile, 1), "colorMap", 0) Else ShaderTexture(shader, CreateTexture(32, 32), "colorMap", 0)
    If(keyNM) Then ShaderTexture(shader, LoadTexture(NMfile, 1), "normalMap", 1) Else ShaderTexture(shader, CreateTexture(32, 32), "normalMap", 1)
    If(keyPR) Then ShaderTexture(shader, LoadTexture(PRfile, 1), "propMap", 2) Else ShaderTexture(shader, CreateTexture(32, 32), "propMap", 2)
    If(keyME) Then ShaderTexture(shader, LoadTexture(MEfile, 1), "metallicMap", 3) Else ShaderTexture(shader, CreateTexture(32, 32), "metallicMap", 3)
    If(keyEM) Then ShaderTexture(shader, LoadTexture(EMfile, 1), "emissionMap", 4) Else ShaderTexture(shader, CreateTexture(32, 32), "emissionMap", 4)
   
    ' Environment Mapping Texture: Shiny reflective on metals or dull on non-metals
    If ismetal Then ShaderTexture(shader, ENVMAP, "envMap", 5) Else ShaderTexture(shader, CreateTexture(32 * 6, 32, 1 + 128), "envMap", 5)

    ' Set Environmental Map
    SetFloat3(shader, "envPos", Float(EntityX(model, 1)), Float(EntityY(model, 1)), Float(EntityZ(model, 1)))
    SetInteger(shader, "setENV", ismetal)

    ' Set additional attributes
    setFloat(shader, "levelscale", levelscale) ' Scale
    setFloat(shader, "gamma", 1.0)             ' Gamma

    ' Texture Scale and Offset
    SetFloat2(shader, "texscale", texscale
, texscale[1])
    SetFloat2(shader, "texoffset", texscale[2], texscale[3])
    ' OpenGL lights
    For Local i:Int = 0 To 7 ; setFloat(shader, "lightradius[" + i + "].Float", OPENGLIGHTS_RANGE[i]) ; Next
    ' Flag: Metallic Surface?
    SetInteger(shader, "isMetal", ismetal)
    ' Other Flags
    SetInteger(shader, "flagPB", 1) ' PBR Shading
    SetInteger(shader, "flagPM", 1) ' Parallax Mapping
    SetInteger(shader, "flagEN", 1) ' Environment Mapping
    SetInteger(shader, "flagEM", 1) ' Emissive
    ' Light Falloff - see https://www.desmos.com/calculator/nmnaud1hrw
    setFloat(shader, "A", 1.0)    ' Light: A value
    setFloat(shader, "B", 10.0)    ' Light: B value
End Function

The Vertex Shader:
#version 130

#define NUM_LIGHTS 8

// ----------------------------------------------------------------------------

struct FloatArray {
    float Float;
};

// ----------------------------------------------------------------------------

// input variables
uniform float ambFactor;
uniform FloatArray lightradius[NUM_LIGHTS];

// ----------------------------------------------------------------------------

// output variables to fragment shader
out vec2 Vertex_UV;
out vec4 Vertex_Color;
out vec4 Vertex_Position;
out vec3 Vertex_View_Vector;
out vec3 Vertex_Surface_to_Viewer_Direction;
out vec3 Vertex_Normal;
out vec3 Vertex_Ambient_Color;
out vec3 Vertex_LightDir[NUM_LIGHTS];
out vec3 Vertex_LightColor[NUM_LIGHTS];
out float Vertex_LightRange[NUM_LIGHTS];
out float Vertex_LightDir_Length[NUM_LIGHTS];

// ----------------------------------------------------------------------------

void main()
{
    Vertex_Color = gl_Color;
    Vertex_Normal = normalize(gl_NormalMatrix * gl_Normal);
    Vertex_Position = gl_ModelViewMatrix * gl_Vertex;

    for (int i = 0; i < NUM_LIGHTS; ++i)
    {
        Vertex_LightDir[i] = gl_LightSource[i].position.xyz - Vertex_Position.xyz;
        Vertex_LightDir_Length[i] = length(normalize(Vertex_LightDir[i]));
        Vertex_LightColor[i] = gl_LightSource[i].diffuse.rgb;
        Vertex_LightRange[i] = lightradius[i].Float;// * lightradius[i].Float;
    }

    Vertex_UV = gl_MultiTexCoord0.xy;
    Vertex_Ambient_Color = gl_LightModel.ambient.rgb * ambFactor;
    Vertex_View_Vector = -Vertex_Position.xyz;
   
    vec3 vViewModelPosition = vec3(gl_ModelViewMatrixInverse * vec4(0, 0, 0, 1.0));
    vec3 vLocalSurfaceToViewerDirection = normalize(vViewModelPosition-gl_Vertex.xyz);
    vec3 vvLocalSurfaceNormal = normalize(gl_Normal);
   
    Vertex_Surface_to_Viewer_Direction = normalize(reflect(vLocalSurfaceToViewerDirection, vvLocalSurfaceNormal)) ;
   
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

The Fragment Shader:
#version 130

#define NUM_LIGHTS 8

// ----------------------------------------------------------------------------

struct FloatArray {
    float Float;
};

// ----------------------------------------------------------------------------

const float PI = 3.14159265359;
const float POscale = 1.0/32;
const float POsteps = 32;
const float POmulti = 1.0;
   
// ----------------------------------------------------------------------------

uniform sampler2D albedoMap;
uniform sampler2D normalMap;
uniform sampler2D propMap;
uniform sampler2D metallicMap;
uniform sampler2D emissionMap;
uniform samplerCube envMap;

// ----------------------------------------------------------------------------

in vec2 Vertex_UV;
in vec2 Vertex_UVB;
in vec4 Vertex_Color;
in vec3 Vertex_Normal;
in vec3 Vertex_NormalCube;
in vec3 Vertex_Ambient_Color;
in vec3 Vertex_View_Vector;
in vec3 Vertex_Surface_to_Viewer_Direction;
in vec4 Vertex_Position;
in vec3 Vertex_LightDir[NUM_LIGHTS];
in vec3 Vertex_LightColor[NUM_LIGHTS];
in float Vertex_LightRange[NUM_LIGHTS];
in float Vertex_LightDir_Length[NUM_LIGHTS];
   
out vec4 FragColor;

// ----------------------------------------------------------------------------

// fixed values
uniform float levelscale;
uniform float gamma;
uniform vec2 texscale;
uniform vec2 texoffset;
uniform vec3 envPos;

// variable values
uniform FloatArray lightradius[NUM_LIGHTS];

// ----------------------------------------------------------------------------

// variable Flags
uniform int flagPB;
uniform int flagPM;
uniform int flagEN;
uniform int flagEM;
uniform int setENV;
uniform int isMetal;

// ----------------------------------------------------------------------------

// texture existance flags
uniform int texAL;
uniform int texNM;
uniform int texPR;
uniform int texME;
uniform int texEM;

// ----------------------------------------------------------------------------

// light attenuation variables
uniform float A;
uniform float B;

// ----------------------------------------------------------------------------

mat3 cotangent_frame(vec3 N, vec3 p, vec2 uv)
{
    vec3 dp1 = dFdx(p);
    vec3 dp2 = dFdy(p);
    vec2 duv1 = dFdx(uv);
    vec2 duv = dFdy(uv);
 
    vec3 dp2perp = cross(dp2, N);
    vec3 dp1perp = cross(N, dp1);
    vec3 T = dp2perp * duv1.x + dp1perp * duv.x;
    vec3 B = dp2perp * duv1.y + dp1perp * duv.y;
 
    float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
    return mat3(T * invmax, B * invmax, N);
}

// ----------------------------------------------------------------------------

vec3 perturb_normal(vec3 N, vec3 V, vec3 map, vec2 texcoord)
{
    map = map * 255.0 / 127.0 - 128.0 / 127.0;
   
    mat3 TBN = cotangent_frame(N, -V, texcoord);
    return normalize(TBN * map);
}

// ----------------------------------------------------------------------------

vec3 ToneMapPBR(vec3 color)
{
    // HDR tonemapping and gamma correction
    color = color / (color + vec3(1.0));
    color = pow(color, vec3(1.0 / gamma));
   
    return color;
}

// ----------------------------------------------------------------------------

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 NdotH2 = NdotH * NdotH;

    float nom = a2;
    float denom = (NdotH2 * (a2 - 1.0) + 1.0);
    denom = PI * denom * denom;

    return nom / denom;
}

// ----------------------------------------------------------------------------

float GeometrySchlickGGX(float NdotV, float roughness)
{
    float r = (roughness + 1.0);
    float k = (r * r) / 8.0;

    float nom = NdotV;
    float denom = NdotV * (1.0 - k) + k;

    return nom / denom;
}

// ----------------------------------------------------------------------------

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 ggx2 = GeometrySchlickGGX(NdotV, roughness);
    float ggx1 = GeometrySchlickGGX(NdotL, roughness);

    return ggx1 * ggx2;
}

// ----------------------------------------------------------------------------

vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
    if(cosTheta > 1.0)
        cosTheta = 1.0;
    float p = pow(1.0 - cosTheta,5.0);
    return F0 + (1.0 - F0) * p;
}

// ----------------------------------------------------------------------------

float CalcAtt(float distance, float range, float a, float b)
{
    return 1.0 / (1.0 + a * distance + b * distance * distance);
}

// ----------------------------------------------------------------------------

// Parallax Occlusion Mapping
vec2 Parallax_Occlusion_Mapping( in vec2 uv, in vec3 view)
{  
    float numLayers = POsteps;
    float layerDepth = 1.0 / numLayers;
   
    vec2 p = view.xy * POscale;
    vec2 deltaUVs = p / numLayers;
   
    float Texd = normalize(texture(propMap,uv).b)*POmulti;

    float d = 0.0;
    while( d < Texd )
    {
        uv -= deltaUVs;
       
        Texd = texture(propMap,uv).b*POmulti;
       
        d += layerDepth; 
    }

    vec2 lastUVs = uv + deltaUVs;
   
    float after = Texd - d;
    float before = (texture(propMap,lastUVs).b) - d + layerDepth;
   
    float w = after / (after - before);
   
    return mix( uv, lastUVs, w );
}

// ----------------------------------------------------------------------------

// Contrast Matrix
mat4 contrastMatrix(float contrast)
{
    float t = ( 1.0 - contrast ) / 2.0;
   
    return mat4(contrast, 0, 0, 0,
        0, contrast, 0, 0,
        0, 0, contrast, 0,
        t, t, t, 1);

}

// ----------------------------------------------------------------------------

void main()
{
    // Texture coordinates
    vec2 ts=texscale;
    vec2 uv = Vertex_UV;
    ts.y=-ts.y;
    uv = (uv * ts) + texoffset;
   
    // TBN Matrix
    vec3 VN = normalize(Vertex_Normal);
    vec3 VV = Vertex_View_Vector;
    mat3 TBN = cotangent_frame(VN, -VV, uv.st);
   
    // Parallax Mapping
    if(flagPM > 0) { uv = Parallax_Occlusion_Mapping(uv, normalize(-VV * TBN)); }   

    // Albedo Texture
    vec4 albedo = vec4(0.5, 0.5, 0.5, 1.0);
    if(texAL > 0)
    {
        albedo = texture(albedoMap, uv);
        albedo.rgb = pow(albedo.rgb, vec3(2.2));
    }

    // Normalmap Texture
    vec3 nrm = Vertex_Normal;
    if(texNM > 0) { nrm = texture(normalMap, uv).rgb; }   
       
    // 3. Perturbated Normals
    vec3 N = Vertex_Normal.xyz;
    vec3 V = normalize(Vertex_View_Vector);
    vec3 PN;
    vec3 PNC;
    if(texNM == 0)
    {
        PN = N;
        nrm = vec3(0.0, 0.0, 1.0);
    }
    else
    {
        vec3 VN = normalize(N);
        vec3 VV = normalize(Vertex_View_Vector);
        mat3 TBN = cotangent_frame(VN, -VV, uv.st);
       
        PN = perturb_normal(VN, VV, nrm, uv);
        PNC = perturb_normal(Vertex_NormalCube, VV, nrm, uv);
    }   
   
    // PBR Texture
    float ao = 1.0;
    float roughness = 0.5;
    float metallic = 0.5;
   
    // Roughness
    if(texPR > 0)
    {
        vec3 pbr = texture(propMap, uv).rgb;
               
        ao = pbr.r;
        roughness = pbr.g;
    }
   
    // Metallic
    if(texME > 0)
    {
        metallic = texture(metallicMap, uv).r;
    }
               
    // Emissive
    vec3 emission = vec3(0.0);
    if(texEM > 0){emission = texture(emissionMap, uv).rgb * 2.0;}

    // Ambient
    vec3 ambient = Vertex_Ambient_Color * albedo.rgb;

    // PBR Lighting
    vec3 Lo = vec3(0.0);
    vec3 irradiance;
    vec3 diffuse=albedo.rgb;
    vec3 reflectDir;
   
    // Reflection
    vec3 NormalizedRSTVD = normalize(Vertex_Surface_to_Viewer_Direction);
   
    if(flagPB > 0)
    {
        // calculate reflectance at normal incidence; if dia-electric (like plastic) use F0
        // of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)   
        vec3 F0 = vec3(0.04);
        F0 = mix(F0, albedo.rgb, metallic);
       
        for(int i = 0; i < NUM_LIGHTS; ++i)
        {
            // calculate per-light radiance
            vec3 L = normalize(Vertex_LightDir[i]);
            vec3 H = normalize(V + L);
            float distance = Vertex_LightDir_Length[i];
            float attenuation = CalcAtt(distance, Vertex_LightRange[i], A, B);
            vec3 radiance = Vertex_LightColor[i] * attenuation;
           
            // Cook-Torrance BRDF
            float NDF = DistributionGGX(PN, H, roughness);
            float G = GeometrySmith(PN, V, L, roughness);
            vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
              
            // specularity
            vec3 nominator = NDF * G * F;
            float denominator = 4.0 * max(dot(PN, V), 0.0) * max(dot(PN, L), 0.0) + 0.001;
            vec3 specular = nominator / denominator;
           
            // kS is equal to Fresnel
            vec3 kS = F;

            // for energy conservation, the diffuse and specular light can't
            // be above 1.0 (unless the surface emits light); to preserve this
            // relationship the diffuse component (kD) should equal 1.0 - kS.
            vec3 kD = vec3(1.0) - kS;

            // multiply kD by the inverse metalness such that only non-metals
            // have diffuse lighting, or a linear blend if partly metal (pure metals
            // have no diffuse light).
            kD *= 1.0-metallic;
               
            // Metallic Reflection --------------------------------------------
           
            diffuse=albedo.rgb;
           
            if(isMetal==1 && flagEN==1 && setENV==1)
            {
                vec3 v=NormalizedRSTVD;
                irradiance = textureCube(envMap, vec3(-v.x,v.y,-v.z)).rgb;
                diffuse = (albedo.rgb * (0.0 + (irradiance * 1.0)));
            }
                           
            // check backface lighting
            float NdotL = max(dot(PN, L), 0.0);
           
            Lo += (kD * diffuse.rgb / PI + (specular)) * Vertex_LightRange[i] * radiance * NdotL;
        }
    }
    // PBR off
    else
    {
        for(int i = 0; i < NUM_LIGHTS; ++i)
        {
            float distance = Vertex_LightDir_Length[i];
            float attenuation = CalcAtt(distance, Vertex_LightRange[i], A, B);
            vec3 radiance = Vertex_LightColor[i] * attenuation;
           
            vec3 L = normalize(Vertex_LightDir[i]);
            vec3 N = Vertex_Normal.xyz;
           
            float NdotL = max(dot(N, L), 0.0);
            Lo += (diffuse.rgb / PI) * Vertex_LightRange[i] * radiance * NdotL;
        }
    }
   
    // mix final lighting with ambient
    vec3 color=(Lo+ambient);
       
    // Ambient Occlusion
    color *= ao;
   
    // more contrast using normals and contrast matrix
    float NL = max(dot(PN, normalize(Vertex_View_Vector)), 0.0);
    color*=NL;
    color.rgb*=(vec4(1.0) * contrastMatrix(2.0)).rgb;   

    // Tonemapping
    color = ToneMapPBR(color);
   
    // Gamma correction
    color = pow(color, vec3(1.0/2.2));
   
    if(flagEM==0){emission=vec3(0.0);}
           
    // Final olor plus Emissive with Alpha
    FragColor = vec4(color+emission, albedo.a);
   
}

Title: Re: OpenB3D PBR Shader Demo
Post by: markcwm on March 02, 2023, 22:40:20
Wow! Really cool. I assume this is your updated PBR code, the code looks great.

I've been foolishly trying to get it working in SL 120 (GL 2.1) without success. I'll test on a SL 130 machine tomorrow.

Thanks.
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on March 03, 2023, 01:35:37
Yes the code has been reviewed and I tried to make it look like the Preview in Substance Player, Substance Painter or even Blender. I think it should be correct, at least visually. And Version 130 is minimum.

I tried to comment as much as possible, as it is a quite complex code. And there are still too many If...Then clauses in the Shader, this could be also optimized further. There is no self-shadowing implemented yet, a Relief Occlusion Mapping would look better then Parallax Occlusion (but also caused a system freeze here in Wireframe mode - I have to figure out what is wrong) and the reflections are static/generic (a cloud scene), but you can capture once a cubemap at the object's position and apply it that you have realistic per-object reflections.

One addition: in general, the combined PBR-Texture like Sketchfab and other tools use it is: R/G/B = AO/Roughness/Metallic plus a separate Heightmap texture. But I've decided to use the Heightmap instead of Metallic as the third one as not all textures are metallic, but all have a height. And so I can determine by the pure existence of a Metallic Texture that is is a Metal, which has a different reflection than a non-metal.

An alternative to that concept would be: add the Heightmap as the Alpha channel in the Basecolor Texture and use for the PBR the standard R/G/B = AO/Roughness/Metallic and always perform a quick check on loading (for example: sample points) over the Metallic Texture if it is black or "not so black". If it is black, it is not a metal (pure black also gives an awful reflection). This would save an additional texture to load.

But for now, I'm happy that it works so well in Blitzmax NG and OpenB3D.
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on March 04, 2023, 16:30:43
I've found a bug in my Parallax Occlusion Mapping shader and fixed it (download link stays the same). I've also added a third small demo to show the Parallax Occlusion Mapping effect on a rounded cube only. You can play with the "POmulti" variable in the PBR.frag shader to intensify the effect.

ShaderExample_POM.jpg
Fixed pbr.vert shader:
#version 130

#define NUM_LIGHTS 8

// ----------------------------------------------------------------------------

struct FloatArray {
float Float;
};

// ----------------------------------------------------------------------------

// input variables
uniform float ambFactor;
uniform FloatArray lightradius[NUM_LIGHTS];

// ----------------------------------------------------------------------------

// output variables to fragment shader
out vec2 Vertex_UV;
out vec4 Vertex_Color;
out vec4 Vertex_Position;
out vec3 Vertex_View_Vector;
out vec3 Vertex_Surface_to_Viewer_Direction;
out vec3 Vertex_Normal;
out vec3 Vertex_Ambient_Color;
out vec3 Vertex_LightDir[NUM_LIGHTS];
out vec3 Vertex_LightColor[NUM_LIGHTS];
out float Vertex_LightRange[NUM_LIGHTS];
out float Vertex_LightDir_Length[NUM_LIGHTS];

// ----------------------------------------------------------------------------

void main()
{
Vertex_Color = gl_Color;
Vertex_Normal = normalize(gl_NormalMatrix * gl_Normal);
Vertex_Position = gl_ModelViewMatrix * gl_Vertex;

for (int i = 0; i < NUM_LIGHTS; ++i)
{
Vertex_LightDir[i] = gl_LightSource[i].position.xyz - Vertex_Position.xyz;
Vertex_LightDir_Length[i] = length(normalize(Vertex_LightDir[i]));
Vertex_LightColor[i] = gl_LightSource[i].diffuse.rgb;
Vertex_LightRange[i] = lightradius[i].Float;
}

Vertex_UV = gl_MultiTexCoord0.xy;
Vertex_Ambient_Color = gl_LightModel.ambient.rgb * ambFactor;
Vertex_View_Vector = -Vertex_Position.xyz;

vec3 vViewModelPosition = vec3(gl_ModelViewMatrixInverse * vec4(0, 0, 0, 1.0));
vec3 vLocalSurfaceToViewerDirection = normalize(vViewModelPosition-gl_Vertex.xyz);
vec3 vvLocalSurfaceNormal = normalize(gl_Normal);

Vertex_Surface_to_Viewer_Direction = normalize(reflect(vLocalSurfaceToViewerDirection, vvLocalSurfaceNormal)) ;

gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

 Fixed pbr.frag shader:
#version 130

#define NUM_LIGHTS 8

// ----------------------------------------------------------------------------

struct FloatArray {
    float Float;
};

// ----------------------------------------------------------------------------

const float PI = 3.14159265359;
const float POscale = 0.05;
const float POmin = 8;
const float POmax = 32;
const float POmulti = 1.0;

// ----------------------------------------------------------------------------

uniform sampler2D albedoMap;
uniform sampler2D normalMap;
uniform sampler2D propMap;
uniform sampler2D metallicMap;
uniform sampler2D emissionMap;
uniform samplerCube envMap;

// ----------------------------------------------------------------------------

in vec2 Vertex_UV;
in vec2 Vertex_UVB;
in vec4 Vertex_Color;
in vec3 Vertex_Normal;
in vec3 Vertex_NormalCube;
in vec3 Vertex_Ambient_Color;
in vec3 Vertex_View_Vector;
in vec3 Vertex_Surface_to_Viewer_Direction;
in vec4 Vertex_Position;
in vec3 Vertex_LightDir[NUM_LIGHTS];
in vec3 Vertex_LightColor[NUM_LIGHTS];
in float Vertex_LightRange[NUM_LIGHTS];
in float Vertex_LightDir_Length[NUM_LIGHTS];

out vec4 FragColor;

// ----------------------------------------------------------------------------

// fixed values
uniform float levelscale;
uniform float gamma;
uniform vec2 texscale;
uniform vec2 texoffset;
uniform vec3 envPos;

// variable values
uniform FloatArray lightradius[NUM_LIGHTS];

// ----------------------------------------------------------------------------

// variable Flags
uniform int flagPB;
uniform int flagPM;
uniform int flagEN;
uniform int flagEM;
uniform int setENV;
uniform int isMetal;

// ----------------------------------------------------------------------------

// texture existance flags
uniform int texAL;
uniform int texNM;
uniform int texPR;
uniform int texME;
uniform int texEM;

// ----------------------------------------------------------------------------

// light attenuation variables
uniform float A;
uniform float B;

// ----------------------------------------------------------------------------

mat3 cotangent_frame(vec3 N, vec3 p, vec2 uv)
{
vec3 dp1 = dFdx(p);
vec3 dp2 = dFdy(p);
vec2 duv1 = dFdx(uv);
vec2 duv = dFdy(uv);
 
vec3 dp2perp = cross(dp2, N);
vec3 dp1perp = cross(N, dp1);
vec3 T = dp2perp * duv1.x + dp1perp * duv.x;
vec3 B = dp2perp * duv1.y + dp1perp * duv.y;
 
float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
return mat3(T * invmax, B * invmax, N);
}

// ----------------------------------------------------------------------------

vec3 perturb_normal(vec3 N, vec3 V, vec3 map, vec2 texcoord)
{
map = map * 255.0 / 127.0 - 128.0 / 127.0;

mat3 TBN = cotangent_frame(N, -V, texcoord);
return normalize(TBN * map);
}

// ----------------------------------------------------------------------------

vec3 ToneMapPBR(vec3 color)
{
// HDR tonemapping and gamma correction
color = color / (color + vec3(1.0));
color = pow(color, vec3(1.0 / gamma));

return color;
}

// ----------------------------------------------------------------------------

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 NdotH2 = NdotH * NdotH;

float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;

return nom / denom;
}

// ----------------------------------------------------------------------------

float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r * r) / 8.0;

float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;

return nom / denom;
}

// ----------------------------------------------------------------------------

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 ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);

return ggx1 * ggx2;
}

// ----------------------------------------------------------------------------

vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
if(cosTheta > 1.0)
cosTheta = 1.0;
float p = pow(1.0 - cosTheta,5.0);
return F0 + (1.0 - F0) * p;
}

// ----------------------------------------------------------------------------

float CalcAtt(float distance, float range, float a, float b)
{
return 1.0 / (1.0 + a * distance + b * distance * distance);
}

// ----------------------------------------------------------------------------

// Contrast Matrix
mat4 contrastMatrix(float contrast)
{
float t = ( 1.0 - contrast ) / 2.0;
   
return mat4(contrast, 0, 0, 0,
0, contrast, 0, 0,
0, 0, contrast, 0,
t, t, t, 1);

}

// ----------------------------------------------------------------------------

vec2 ParallaxOcclusionMapping(vec2 texCoords, vec3 viewDir)
{
    // number of depth layers
    float numLayers = mix(POmax, POmin, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));

    // calculate the size of each layer
    float layerDepth = 1.0 / numLayers;

    // depth of current layer
    float currentLayerDepth = 0.0;

    // the amount to shift the texture coordinates per layer (from vector P)
    vec2 P = viewDir.xy / viewDir.z * POscale * POmulti;
    vec2 deltaTexCoords = P / numLayers;

    // get initial values
    vec2  currentTexCoords     = texCoords;
    float currentDepthMapValue = texture(propMap, currentTexCoords).b;

    while(currentLayerDepth < currentDepthMapValue)
    {
        // shift texture coordinates along direction of P
        currentTexCoords -= deltaTexCoords;

        // get depthmap value at current texture coordinates
        currentDepthMapValue = texture(propMap, currentTexCoords).b;

        // get depth of next layer
        currentLayerDepth += layerDepth;
    }

    // get texture coordinates before collision (reverse operations)
    vec2 prevTexCoords = currentTexCoords + deltaTexCoords;

    // get depth after and before collision for linear interpolation
    float afterDepth  = currentDepthMapValue - currentLayerDepth;
    float beforeDepth = texture(propMap, prevTexCoords).b - currentLayerDepth + layerDepth;

    // interpolation of texture coordinates
    float weight = afterDepth / (afterDepth - beforeDepth);
    vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);

    return finalTexCoords;
}

mat3 computeTBN(vec2 tempUv, vec3 worldPos, vec3 worldNormal){
    vec3 Q1  = dFdx(worldPos);
    vec3 Q2  = dFdy(worldPos);
    vec2 st1 = dFdx(tempUv);
    vec2 st2 = dFdy(tempUv);

    vec3 n   = normalize(worldNormal);
    vec3 t  = normalize(Q1*st2.t - Q2*st1.t);

    // or directly compute from the equation
    vec3 b = normalize(-Q1*st2.s + Q2*st1.s);

    mat3 tbn = mat3(t, b, n);

    return tbn;
}

// ----------------------------------------------------------------------------

void main()
{
// Texture coordinates
vec2 ts=texscale;
vec2 uv = Vertex_UV;
//ts.y=-ts.y;
uv = (uv * ts) + texoffset;

// TBN Matrix
vec3 VN = normalize(Vertex_Normal);
vec3 VV = Vertex_View_Vector;
//mat3 TBN = computeTBN(uv.st,-VV,Vertex_Normal);
mat3 TBN = cotangent_frame(VN, VV, uv.st);

// Parallax Mapping
if(flagPM > 0)
{
uv = ParallaxOcclusionMapping(uv, normalize(-VV * TBN));

}

// Albedo Texture
vec4 albedo = vec4(0.5, 0.5, 0.5, 1.0);
if(texAL > 0)
{
albedo = texture(albedoMap, uv);
albedo.rgb = pow(albedo.rgb, vec3(2.2));
}

// Normalmap Texture
vec3 nrm = Vertex_Normal;
if(texNM > 0)
{
nrm = texture(normalMap, uv).rgb;
//nrm.y=1.0-nrm.y;
}

// 3. Perturbated Normals
vec3 N = Vertex_Normal.xyz;
vec3 V = normalize(Vertex_View_Vector);
vec3 PN;
vec3 PNC;
if(texNM == 0)
{
PN = N;
nrm = vec3(0.0, 0.0, 1.0);
}
else
{
vec3 VN = normalize(N);
vec3 VV = normalize(Vertex_View_Vector);
//mat3 TBN = cotangent_frame(VN, -VV, uv.st);

PN = perturb_normal(VN, VV, nrm, uv);
PNC = perturb_normal(Vertex_NormalCube, VV, nrm, uv);
}

// PBR Texture
float ao = 1.0;
float roughness = 0.5;
float metallic = 0.5;

// Roughness
if(texPR > 0)
{
vec3 pbr = texture(propMap, uv).rgb;

ao = pbr.r;
roughness = pbr.g;
}

// Metallic
if(texME > 0)
{
metallic = texture(metallicMap, uv).r;
}

// Emissive
vec3 emission = vec3(0.0);
if(texEM > 0){emission = texture(emissionMap, uv).rgb * 2.0;}

// Ambient
vec3 ambient = Vertex_Ambient_Color * albedo.rgb;

// PBR Lighting
vec3 Lo = vec3(0.0);
vec3 irradiance;
vec3 diffuse=albedo.rgb;
vec3 reflectDir;

// Reflection
vec3 NormalizedRSTVD = normalize(Vertex_Surface_to_Viewer_Direction);

if(flagPB > 0)
{
// calculate reflectance at normal incidence; if dia-electric (like plastic) use F0
// of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)   
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo.rgb, metallic);

for(int i = 0; i < NUM_LIGHTS; ++i)
{
// calculate per-light radiance
vec3 L = normalize(Vertex_LightDir[i]);
vec3 H = normalize(V + L);
float distance = Vertex_LightDir_Length[i];
float attenuation = CalcAtt(distance, Vertex_LightRange[i], A, B);
vec3 radiance = Vertex_LightColor[i] * attenuation;

// Cook-Torrance BRDF
float NDF = DistributionGGX(PN, H, roughness);
float G = GeometrySmith(PN, V, L, roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
          
// specularity
vec3 nominator = NDF * G * F;
float denominator = 4.0 * max(dot(PN, V), 0.0) * max(dot(PN, L), 0.0) + 0.001;
vec3 specular = nominator / denominator;

// kS is equal to Fresnel
vec3 kS = F;

// for energy conservation, the diffuse and specular light can't
// be above 1.0 (unless the surface emits light); to preserve this
// relationship the diffuse component (kD) should equal 1.0 - kS.
vec3 kD = vec3(1.0) - kS;

// multiply kD by the inverse metalness such that only non-metals
// have diffuse lighting, or a linear blend if partly metal (pure metals
// have no diffuse light).
kD *= 1.0-metallic;

// Metallic Reflection --------------------------------------------

diffuse=albedo.rgb;

if(isMetal==1 && flagEN==1 && setENV==1)
{
vec3 v=NormalizedRSTVD;
irradiance = textureCube(envMap, vec3(-v.x,v.y,-v.z)).rgb;
diffuse = (albedo.rgb * (0.0 + (irradiance * 1.0)));
}

// check backface lighting
float NdotL = max(dot(PN, L), 0.0);

Lo += (kD * diffuse.rgb / PI + (specular)) * Vertex_LightRange[i] * radiance * NdotL;
}
}
// PBR off
else
{
for(int i = 0; i < NUM_LIGHTS; ++i)
{
float distance = Vertex_LightDir_Length[i];
float attenuation = CalcAtt(distance, Vertex_LightRange[i], A, B);
vec3 radiance = Vertex_LightColor[i] * attenuation;

vec3 L = normalize(Vertex_LightDir[i]);
vec3 N = Vertex_Normal.xyz;

float NdotL = max(dot(N, L), 0.0);
Lo += (diffuse.rgb / PI) * Vertex_LightRange[i] * radiance * NdotL;
}
}

// mix final lighting with ambient
vec3 color=(Lo+ambient);

// Ambient Occlusion
color *= ao;

// more contrast using normals and contrast matrix
float NL = max(dot(PN, normalize(Vertex_View_Vector)), 0.0);
color*=NL;
color.rgb*=(vec4(1.0) * contrastMatrix(2.0)).rgb;

// Tonemapping
color = ToneMapPBR(color);

// Gamma correction
color = pow(color, vec3(1.0/2.2));

if(flagEM==0){emission=vec3(0.0);}

// Final olor plus Emissive with Alpha
FragColor = vec4(color+emission, albedo.a);

}
Title: Re: OpenB3D PBR Shader Demo
Post by: markcwm on March 04, 2023, 23:30:44
Thanks again Krischan. Is 'Parallax occlusion' a type of bump mapping?

I've just tested in Windows 8.1 using OGL 4.3 and there is unfortunately an issue that makes the triple PBR example and POM example surfaces almost black, the minimal example shows as metallic red reflecting clouds but I'm not sure what it is supposed to look like. Here are some screenies.

(https://i.imgur.com/SLKchMe.png)
(https://i.imgur.com/Gcmm6rW.png)
(https://i.imgur.com/bvQgMV5.png)
As you can see there is a reflective surface in the POM example but only at certain angles but the triple PBR example is black at all angles.

It's likely that one of the GLSL commands doesn't work on my graphics card inputing zero data at some point. So here is the output of hardwareinfo.bmx to check if something is missing.

Hardwareinfo:

Width:  1366
Height: 768
Depth:  32
Hertz:  60

Vendor:         Intel
Renderer:       Intel(R) HD Graphics 4400
OpenGL-Version: 4.3.0 - Build 10.18.14.4414

Max Texture Units: 8
Max Texture Size:  16384
Max Lights:        8

OpenGL Extensions:
GL_EXT_blend_minmax
GL_EXT_blend_subtract
GL_EXT_blend_color
GL_EXT_abgr
GL_EXT_texture3D
GL_EXT_clip_volume_hint
GL_EXT_compiled_vertex_array
GL_SGIS_texture_edge_clamp
GL_SGIS_generate_mipmap
GL_EXT_draw_range_elements
GL_SGIS_texture_lod
GL_EXT_rescale_normal
GL_EXT_packed_pixels
GL_EXT_texture_edge_clamp
GL_EXT_separate_specular_color
GL_ARB_multitexture
GL_ARB_map_buffer_alignment
GL_ARB_conservative_depth
GL_EXT_texture_env_combine
GL_EXT_bgra
GL_EXT_blend_func_separate
GL_EXT_secondary_color
GL_EXT_fog_coord
GL_EXT_texture_env_add
GL_ARB_texture_cube_map
GL_ARB_transpose_matrix
GL_ARB_internalformat_query
GL_ARB_internalformat_query2
GL_ARB_texture_env_add
GL_IBM_texture_mirrored_repeat
GL_EXT_multi_draw_arrays
GL_SUN_multi_draw_arrays
GL_NV_blend_square
GL_ARB_texture_compression
GL_3DFX_texture_compression_FXT1
GL_EXT_texture_filter_anisotropic
GL_ARB_texture_border_clamp
GL_ARB_point_parameters
GL_ARB_texture_env_combine
GL_ARB_texture_env_dot3
GL_ARB_texture_env_crossbar
GL_EXT_texture_compression_s3tc
GL_ARB_shadow
GL_ARB_window_pos
GL_EXT_shadow_funcs
GL_EXT_stencil_wrap
GL_ARB_vertex_program
GL_EXT_texture_rectangle
GL_ARB_fragment_program
GL_EXT_stencil_two_side
GL_ATI_separate_stencil
GL_ARB_vertex_buffer_object
GL_EXT_texture_lod_bias
GL_ARB_occlusion_query
GL_ARB_fragment_shader
GL_ARB_shader_objects
GL_ARB_shading_language_100
GL_ARB_texture_non_power_of_two
GL_ARB_vertex_shader
GL_NV_texgen_reflection
GL_ARB_point_sprite
GL_ARB_fragment_program_shadow
GL_EXT_blend_equation_separate
GL_ARB_depth_texture
GL_ARB_texture_rectangle
GL_ARB_draw_buffers
GL_ARB_color_buffer_float
GL_ARB_half_float_pixel
GL_ARB_texture_float
GL_ARB_pixel_buffer_object
GL_EXT_framebuffer_object
GL_ARB_draw_instanced
GL_ARB_half_float_vertex
GL_ARB_occlusion_query2
GL_EXT_draw_buffers2
GL_WIN_swap_hint
GL_EXT_texture_sRGB
GL_ARB_multisample
GL_EXT_packed_float
GL_EXT_texture_shared_exponent
GL_ARB_texture_rg
GL_ARB_texture_compression_rgtc
GL_NV_conditional_render
GL_ARB_texture_swizzle
GL_EXT_texture_swizzle
GL_ARB_texture_gather
GL_ARB_sync
GL_ARB_cl_event
GL_ARB_framebuffer_sRGB
GL_EXT_packed_depth_stencil
GL_ARB_depth_buffer_float
GL_EXT_transform_feedback
GL_ARB_transform_feedback2
GL_ARB_draw_indirect
GL_EXT_framebuffer_blit
GL_EXT_framebuffer_multisample
GL_ARB_framebuffer_object
GL_ARB_framebuffer_no_attachments
GL_EXT_texture_array
GL_EXT_texture_integer
GL_ARB_map_buffer_range
GL_ARB_texture_buffer_range
GL_EXT_texture_snorm
GL_ARB_blend_func_extended
GL_INTEL_performance_query
GL_ARB_copy_buffer
GL_ARB_sampler_objects
GL_NV_primitive_restart
GL_ARB_seamless_cube_map
GL_ARB_uniform_buffer_object
GL_ARB_depth_clamp
GL_ARB_vertex_array_bgra
GL_ARB_shader_bit_encoding
GL_ARB_draw_buffers_blend
GL_ARB_geometry_shader4
GL_EXT_geometry_shader4
GL_ARB_texture_query_lod
GL_ARB_explicit_attrib_location
GL_ARB_draw_elements_base_vertex
GL_ARB_instanced_arrays
GL_ARB_base_instance
GL_ARB_fragment_coord_conventions
GL_EXT_gpu_program_parameters
GL_ARB_texture_buffer_object_rgb32
GL_ARB_compatibility
GL_ARB_texture_rgb10_a2ui
GL_ARB_texture_multisample
GL_ARB_vertex_type_2_10_10_10_rev
GL_ARB_timer_query
GL_ARB_tessellation_shader
GL_ARB_vertex_array_object
GL_ARB_provoking_vertex
GL_ARB_sample_shading
GL_ARB_texture_cube_map_array
GL_EXT_gpu_shader4
GL_ARB_gpu_shader5
GL_ARB_gpu_shader_fp64
GL_INTEL_fragment_shader_ordering
GL_ARB_fragment_shader_interlock
GL_ARB_clip_control
GL_ARB_shader_subroutine
GL_ARB_transform_feedback3
GL_ARB_get_program_binary
GL_ARB_separate_shader_objects
GL_ARB_shader_precision
GL_ARB_vertex_attrib_64bit
GL_ARB_viewport_array
GL_ARB_transform_feedback_instanced
GL_ARB_compressed_texture_pixel_storage
GL_ARB_shader_atomic_counters
GL_ARB_shading_language_packing
GL_ARB_shader_image_load_store
GL_ARB_shading_language_420pack
GL_ARB_texture_storage
GL_EXT_texture_storage
GL_ARB_compute_shader
GL_ARB_vertex_attrib_binding
GL_ARB_texture_view
GL_ARB_fragment_layer_viewport
GL_ARB_multi_draw_indirect
GL_ARB_program_interface_query
GL_ARB_shader_image_size
GL_ARB_shader_storage_buffer_object
GL_ARB_texture_storage_multisample
GL_ARB_buffer_storage
GL_AMD_vertex_shader_layer
GL_AMD_vertex_shader_viewport_index
GL_ARB_query_buffer_object
GL_EXT_polygon_offset_clamp
GL_ARB_debug_output
GL_KHR_debug
GL_ARB_arrays_of_arrays
GL_ARB_texture_query_levels
GL_ARB_invalidate_subdata
GL_ARB_clear_buffer_object
GL_ARB_texture_mirror_clamp_to_edge
GL_INTEL_map_texture
GL_ARB_texture_compression_bptc
GL_ARB_ES2_compatibility
GL_ARB_ES3_compatibility
GL_ARB_robustness
GL_ARB_robust_buffer_access_behavior
GL_EXT_texture_sRGB_decode
GL_ARB_copy_image
GL_KHR_blend_equation_advanced
GL_EXT_direct_state_access
GL_ARB_stencil_texturing
GL_ARB_texture_stencil8
GL_ARB_explicit_uniform_location

- Ready -

Process complete
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on March 05, 2023, 02:25:49
Hmm you are using a Intel graphics card, this may not work correct there. On my Nvidia it looks like that (and has two times the extensions like the Intel)

Hardwareinfo:

Width:  3840
Height: 1600
Depth:  32
Hertz:  75

Vendor:         NVIDIA Corporation
Renderer:       NVIDIA GeForce RTX 2080/PCIe/SSE2
OpenGL-Version: 4.6.0 NVIDIA 528.49

Max Texture Units: 4
Max Texture Size:  32768
Max Lights:        8

OpenGL Extensions:
GL_AMD_multi_draw_indirect
GL_AMD_seamless_cubemap_per_texture
GL_AMD_vertex_shader_viewport_index
GL_AMD_vertex_shader_layer
GL_ARB_arrays_of_arrays
GL_ARB_base_instance
GL_ARB_bindless_texture
GL_ARB_blend_func_extended
GL_ARB_buffer_storage
GL_ARB_clear_buffer_object
GL_ARB_clear_texture
GL_ARB_clip_control
GL_ARB_color_buffer_float
GL_ARB_compatibility
GL_ARB_compressed_texture_pixel_storage
GL_ARB_conservative_depth
GL_ARB_compute_shader
GL_ARB_compute_variable_group_size
GL_ARB_conditional_render_inverted
GL_ARB_copy_buffer
GL_ARB_copy_image
GL_ARB_cull_distance
GL_ARB_debug_output
GL_ARB_depth_buffer_float
GL_ARB_depth_clamp
GL_ARB_depth_texture
GL_ARB_derivative_control
GL_ARB_direct_state_access
GL_ARB_draw_buffers
GL_ARB_draw_buffers_blend
GL_ARB_draw_indirect
GL_ARB_draw_elements_base_vertex
GL_ARB_draw_instanced
GL_ARB_enhanced_layouts
GL_ARB_ES2_compatibility
GL_ARB_ES3_compatibility
GL_ARB_ES3_1_compatibility
GL_ARB_ES3_2_compatibility
GL_ARB_explicit_attrib_location
GL_ARB_explicit_uniform_location
GL_ARB_fragment_coord_conventions
GL_ARB_fragment_layer_viewport
GL_ARB_fragment_program
GL_ARB_fragment_program_shadow
GL_ARB_fragment_shader
GL_ARB_fragment_shader_interlock
GL_ARB_framebuffer_no_attachments
GL_ARB_framebuffer_object
GL_ARB_framebuffer_sRGB
GL_ARB_geometry_shader4
GL_ARB_get_program_binary
GL_ARB_get_texture_sub_image
GL_ARB_gl_spirv
GL_ARB_gpu_shader5
GL_ARB_gpu_shader_fp64
GL_ARB_gpu_shader_int64
GL_ARB_half_float_pixel
GL_ARB_half_float_vertex
GL_ARB_imaging
GL_ARB_indirect_parameters
GL_ARB_instanced_arrays
GL_ARB_internalformat_query
GL_ARB_internalformat_query2
GL_ARB_invalidate_subdata
GL_ARB_map_buffer_alignment
GL_ARB_map_buffer_range
GL_ARB_multi_bind
GL_ARB_multi_draw_indirect
GL_ARB_multisample
GL_ARB_multitexture
GL_ARB_occlusion_query
GL_ARB_occlusion_query2
GL_ARB_parallel_shader_compile
GL_ARB_pipeline_statistics_query
GL_ARB_pixel_buffer_object
GL_ARB_point_parameters
GL_ARB_point_sprite
GL_ARB_polygon_offset_clamp
GL_ARB_post_depth_coverage
GL_ARB_program_interface_query
GL_ARB_provoking_vertex
GL_ARB_query_buffer_object
GL_ARB_robust_buffer_access_behavior
GL_ARB_robustness
GL_ARB_sample_locations
GL_ARB_sample_shading
GL_ARB_sampler_objects
GL_ARB_seamless_cube_map
GL_ARB_seamless_cubemap_per_texture
GL_ARB_separate_shader_objects
GL_ARB_shader_atomic_counter_ops
GL_ARB_shader_atomic_counters
GL_ARB_shader_ballot
GL_ARB_shader_bit_encoding
GL_ARB_shader_clock
GL_ARB_shader_draw_parameters
GL_ARB_shader_group_vote
GL_ARB_shader_image_load_store
GL_ARB_shader_image_size
GL_ARB_shader_objects
GL_ARB_shader_precision
GL_ARB_shader_storage_buffer_object
GL_ARB_shader_subroutine
GL_ARB_shader_texture_image_samples
GL_ARB_shader_texture_lod
GL_ARB_shading_language_100
GL_ARB_shader_viewport_layer_array
GL_ARB_shading_language_420pack
GL_ARB_shading_language_include
GL_ARB_shading_language_packing
GL_ARB_shadow
GL_ARB_sparse_buffer
GL_ARB_sparse_texture
GL_ARB_sparse_texture2
GL_ARB_sparse_texture_clamp
GL_ARB_spirv_extensions
GL_ARB_stencil_texturing
GL_ARB_sync
GL_ARB_tessellation_shader
GL_ARB_texture_barrier
GL_ARB_texture_border_clamp
GL_ARB_texture_buffer_object
GL_ARB_texture_buffer_object_rgb32
GL_ARB_texture_buffer_range
GL_ARB_texture_compression
GL_ARB_texture_compression_bptc
GL_ARB_texture_compression_rgtc
GL_ARB_texture_cube_map
GL_ARB_texture_cube_map_array
GL_ARB_texture_env_add
GL_ARB_texture_env_combine
GL_ARB_texture_env_crossbar
GL_ARB_texture_env_dot3
GL_ARB_texture_filter_anisotropic
GL_ARB_texture_filter_minmax
GL_ARB_texture_float
GL_ARB_texture_gather
GL_ARB_texture_mirror_clamp_to_edge
GL_ARB_texture_mirrored_repeat
GL_ARB_texture_multisample
GL_ARB_texture_non_power_of_two
GL_ARB_texture_query_levels
GL_ARB_texture_query_lod
GL_ARB_texture_rectangle
GL_ARB_texture_rg
GL_ARB_texture_rgb10_a2ui
GL_ARB_texture_stencil8
GL_ARB_texture_storage
GL_ARB_texture_storage_multisample
GL_ARB_texture_swizzle
GL_ARB_texture_view
GL_ARB_timer_query
GL_ARB_transform_feedback2
GL_ARB_transform_feedback3
GL_ARB_transform_feedback_instanced
GL_ARB_transform_feedback_overflow_query
GL_ARB_transpose_matrix
GL_ARB_uniform_buffer_object
GL_ARB_vertex_array_bgra
GL_ARB_vertex_array_object
GL_ARB_vertex_attrib_64bit
GL_ARB_vertex_attrib_binding
GL_ARB_vertex_buffer_object
GL_ARB_vertex_program
GL_ARB_vertex_shader
GL_ARB_vertex_type_10f_11f_11f_rev
GL_ARB_vertex_type_2_10_10_10_rev
GL_ARB_viewport_array
GL_ARB_window_pos
GL_ATI_draw_buffers
GL_ATI_texture_float
GL_ATI_texture_mirror_once
GL_S3_s3tc
GL_EXT_texture_env_add
GL_EXT_abgr
GL_EXT_bgra
GL_EXT_bindable_uniform
GL_EXT_blend_color
GL_EXT_blend_equation_separate
GL_EXT_blend_func_separate
GL_EXT_blend_minmax
GL_EXT_blend_subtract
GL_EXT_compiled_vertex_array
GL_EXT_Cg_shader
GL_EXT_depth_bounds_test
GL_EXT_direct_state_access
GL_EXT_draw_buffers2
GL_EXT_draw_instanced
GL_EXT_draw_range_elements
GL_EXT_fog_coord
GL_EXT_framebuffer_blit
GL_EXT_framebuffer_multisample
GL_EXTX_framebuffer_mixed_formats
GL_EXT_framebuffer_multisample_blit_scaled
GL_EXT_framebuffer_object
GL_EXT_framebuffer_sRGB
GL_EXT_geometry_shader4
GL_EXT_gpu_program_parameters
GL_EXT_gpu_shader4
GL_EXT_multi_draw_arrays
GL_EXT_multiview_texture_multisample
GL_EXT_multiview_timer_query
GL_EXT_packed_depth_stencil
GL_EXT_packed_float
GL_EXT_packed_pixels
GL_EXT_pixel_buffer_object
GL_EXT_point_parameters
GL_EXT_polygon_offset_clamp
GL_EXT_post_depth_coverage
GL_EXT_provoking_vertex
GL_EXT_raster_multisample
GL_EXT_rescale_normal
GL_EXT_secondary_color
GL_EXT_separate_shader_objects
GL_EXT_separate_specular_color
GL_EXT_shader_image_load_formatted
GL_EXT_shader_image_load_store
GL_EXT_shader_integer_mix
GL_EXT_shadow_funcs
GL_EXT_sparse_texture2
GL_EXT_stencil_two_side
GL_EXT_stencil_wrap
GL_EXT_texture3D
GL_EXT_texture_array
GL_EXT_texture_buffer_object
GL_EXT_texture_compression_dxt1
GL_EXT_texture_compression_latc
GL_EXT_texture_compression_rgtc
GL_EXT_texture_compression_s3tc
GL_EXT_texture_cube_map
GL_EXT_texture_edge_clamp
GL_EXT_texture_env_combine
GL_EXT_texture_env_dot3
GL_EXT_texture_filter_anisotropic
GL_EXT_texture_filter_minmax
GL_EXT_texture_integer
GL_EXT_texture_lod
GL_EXT_texture_lod_bias
GL_EXT_texture_mirror_clamp
GL_EXT_texture_object
GL_EXT_texture_shadow_lod
GL_EXT_texture_shared_exponent
GL_EXT_texture_sRGB
GL_EXT_texture_sRGB_R8
GL_EXT_texture_sRGB_decode
GL_EXT_texture_storage
GL_EXT_texture_swizzle
GL_EXT_timer_query
GL_EXT_transform_feedback2
GL_EXT_vertex_array
GL_EXT_vertex_array_bgra
GL_EXT_vertex_attrib_64bit
GL_EXT_window_rectangles
GL_EXT_import_sync_object
GL_IBM_rasterpos_clip
GL_IBM_texture_mirrored_repeat
GL_KHR_context_flush_control
GL_KHR_debug
GL_EXT_memory_object
GL_EXT_memory_object_win32
GL_NV_memory_object_sparse
GL_EXT_win32_keyed_mutex
GL_KHR_parallel_shader_compile
GL_KHR_no_error
GL_KHR_robust_buffer_access_behavior
GL_KHR_robustness
GL_EXT_semaphore
GL_EXT_semaphore_win32
GL_NV_timeline_semaphore
GL_KHR_shader_subgroup
GL_KTX_buffer_region
GL_NV_alpha_to_coverage_dither_control
GL_NV_bindless_multi_draw_indirect
GL_NV_bindless_multi_draw_indirect_count
GL_NV_bindless_texture
GL_NV_blend_equation_advanced
GL_NV_blend_equation_advanced_coherent
GL_NVX_blend_equation_advanced_multi_draw_buffers
GL_NV_blend_minmax_factor
GL_NV_blend_square
GL_NV_clip_space_w_scaling
GL_NV_command_list
GL_NV_compute_program5
GL_NV_compute_shader_derivatives
GL_NV_conditional_render
GL_NV_conservative_raster
GL_NV_conservative_raster_dilate
GL_NV_conservative_raster_pre_snap
GL_NV_conservative_raster_pre_snap_triangles
GL_NV_conservative_raster_underestimation
GL_NV_copy_depth_to_color
GL_NV_copy_image
GL_NV_depth_buffer_float
GL_NV_depth_clamp
GL_NV_draw_texture
GL_NV_draw_vulkan_image
GL_NV_ES1_1_compatibility
GL_NV_ES3_1_compatibility
GL_NV_explicit_multisample
GL_NV_feature_query
GL_NV_fence
GL_NV_fill_rectangle
GL_NV_float_buffer
GL_NV_fog_distance
GL_NV_fragment_coverage_to_color
GL_NV_fragment_program
GL_NV_fragment_program_option
GL_NV_fragment_program2
GL_NV_fragment_shader_barycentric
GL_NV_fragment_shader_interlock
GL_NV_framebuffer_mixed_samples
GL_NV_framebuffer_multisample_coverage
GL_NV_geometry_shader4
GL_NV_geometry_shader_passthrough
GL_NV_gpu_program4
GL_NV_internalformat_sample_query
GL_NV_gpu_program4_1
GL_NV_gpu_program5
GL_NV_gpu_program5_mem_extended
GL_NV_gpu_program_fp64
GL_NV_gpu_shader5
GL_NV_half_float
GL_NV_light_max_exponent
GL_NV_memory_attachment
GL_NV_mesh_shader
GL_NV_multisample_coverage
GL_NV_multisample_filter_hint
GL_NV_occlusion_query
GL_NV_packed_depth_stencil
GL_NV_parameter_buffer_object
GL_NV_parameter_buffer_object2
GL_NV_path_rendering
GL_NV_path_rendering_shared_edge
GL_NV_pixel_data_range
GL_NV_point_sprite
GL_NV_primitive_restart
GL_NV_query_resource
GL_NV_query_resource_tag
GL_NV_register_combiners
GL_NV_register_combiners2
GL_NV_representative_fragment_test
GL_NV_sample_locations
GL_NV_sample_mask_override_coverage
GL_NV_scissor_exclusive
GL_NV_shader_atomic_counters
GL_NV_shader_atomic_float
GL_NV_shader_atomic_float64
GL_NV_shader_atomic_fp16_vector
GL_NV_shader_atomic_int64
GL_NV_shader_buffer_load
GL_NV_shader_storage_buffer_object
GL_NV_shader_subgroup_partitioned
GL_NV_shader_texture_footprint
GL_NV_shading_rate_image
GL_NV_stereo_view_rendering
GL_NV_texgen_reflection
GL_NV_texture_barrier
GL_NV_texture_compression_vtc
GL_NV_texture_env_combine4
GL_NV_texture_multisample
GL_NV_texture_rectangle
GL_NV_texture_rectangle_compressed
GL_NV_texture_shader
GL_NV_texture_shader2
GL_NV_texture_shader3
GL_NV_transform_feedback
GL_NV_transform_feedback2
GL_NV_uniform_buffer_unified_memory
GL_NV_uniform_buffer_std430_layout
GL_NV_vertex_array_range
GL_NV_vertex_array_range2
GL_NV_vertex_attrib_integer_64bit
GL_NV_vertex_buffer_unified_memory
GL_NV_vertex_program
GL_NV_vertex_program1_1
GL_NV_vertex_program2
GL_NV_vertex_program2_option
GL_NV_vertex_program3
GL_NV_viewport_array2
GL_NV_viewport_swizzle
GL_NVX_conditional_render
GL_NVX_linked_gpu_multicast
GL_NV_gpu_multicast
GL_NVX_gpu_multicast2
GL_NVX_progress_fence
GL_NVX_gpu_memory_info
GL_NVX_multigpu_info
GL_NVX_nvenc_interop
GL_NV_shader_thread_group
GL_NV_shader_thread_shuffle
GL_KHR_blend_equation_advanced
GL_KHR_blend_equation_advanced_coherent
GL_OVR_multiview
GL_OVR_multiview2
GL_SGIS_generate_mipmap
GL_SGIS_texture_lod
GL_SGIX_depth_texture
GL_SGIX_shadow
GL_SUN_slice_accum
GL_WIN_swap_hint
WGL_EXT_swap_control

- Ready -
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on March 05, 2023, 02:30:53
I've run it on my second rig which has an AMD processor with built-in graphics and I'm getting the same result like you, here is the log. The question is: why does it run on Nvidia, but not on Intel/AMD?

Hardwareinfo:

Width:  2560
Height: 1440
Depth:  32
Hertz:  59

Vendor:         ATI Technologies Inc.
Renderer:       AMD Radeon(TM) Graphics
OpenGL-Version: 4.6.0 Compatibility Profile Context 22.20.42.221019

Max Texture Units: 8
Max Texture Size:  16384
Max Lights:        8

OpenGL Extensions:
GL_EXT_abgr
GL_EXT_blend_color
GL_EXT_blend_minmax
GL_EXT_blend_subtract
GL_EXT_texture_object
GL_EXT_vertex_array
GL_EXT_compiled_vertex_array
GL_EXT_texture3D
GL_EXT_bgra
GL_EXT_draw_range_elements
GL_EXT_point_parameters
GL_EXT_texture_edge_clamp
GL_ARB_multitexture
GL_ARB_multisample
GL_ARB_texture_cube_map
GL_ARB_texture_env_add
GL_ARB_transpose_matrix
GL_EXT_blend_func_separate
GL_EXT_fog_coord
GL_EXT_multi_draw_arrays
GL_EXT_secondary_color
GL_EXT_texture_env_add
GL_EXT_texture_filter_anisotropic
GL_EXT_texture_lod
GL_EXT_texture_lod_bias
GL_NV_blend_square
WGL_EXT_swap_control
GL_ARB_point_parameters
GL_ARB_texture_border_clamp
GL_ARB_texture_compression
GL_EXT_texture_compression_s3tc
GL_EXT_texture_env_combine
GL_EXT_texture_env_dot3
GL_ARB_shadow
GL_ARB_shadow_ambient
GL_ARB_texture_env_combine
GL_ARB_texture_env_dot3
GL_ARB_depth_texture
GL_ARB_fragment_program
GL_ARB_vertex_program
GL_ARB_window_pos
GL_ATI_draw_buffers
GL_ATI_texture_env_combine3
GL_EXT_shadow_funcs
GL_EXT_stencil_wrap
GL_EXT_texture_rectangle
GL_NV_primitive_restart
GL_S3_s3tc
GL_ARB_fragment_program_shadow
GL_ARB_fragment_shader
GL_ARB_occlusion_query
GL_ARB_point_sprite
GL_ARB_shader_objects
GL_ARB_shading_language_100
GL_ARB_texture_non_power_of_two
GL_ARB_vertex_buffer_object
GL_ARB_vertex_shader
GL_EXT_blend_equation_separate
GL_EXT_depth_bounds_test
GL_ARB_color_buffer_float
GL_ARB_draw_buffers
GL_ARB_half_float_pixel
GL_ARB_pixel_buffer_object
GL_ARB_texture_float
GL_ARB_texture_rectangle
GL_EXT_framebuffer_object
GL_EXT_pixel_buffer_object
GL_OES_draw_texture
GL_EXT_framebuffer_blit
GL_EXT_framebuffer_multisample
GL_EXT_packed_depth_stencil
GL_EXT_texture_format_BGRA8888
GL_ATI_separate_stencil
GL_ATI_shader_texture_lod
GL_EXT_draw_buffers2
GL_EXT_framebuffer_sRGB
GL_EXT_geometry_shader4
GL_EXT_gpu_program_parameters
GL_EXT_gpu_shader4
GL_EXT_texture_array
GL_EXT_texture_buffer_object
GL_EXT_texture_compression_latc
GL_EXT_texture_compression_rgtc
GL_EXT_texture_integer
GL_EXT_transform_feedback
GL_NV_depth_buffer_float
GL_OES_EGL_image
GL_EXT_provoking_vertex
GL_EXT_texture_sRGB
GL_EXT_texture_shared_exponent
GL_ARB_depth_buffer_float
GL_ARB_draw_instanced
GL_ARB_framebuffer_object
GL_ARB_framebuffer_sRGB
GL_ARB_geometry_shader4
GL_ARB_half_float_vertex
GL_ARB_instanced_arrays
GL_ARB_map_buffer_range
GL_ARB_texture_buffer_object
GL_ARB_texture_compression_rgtc
GL_ARB_texture_rg
GL_ARB_transform_feedback
GL_ARB_vertex_array_object
GL_EXT_direct_state_access
GL_EXT_texture_snorm
GL_ARB_compatibility
GL_ARB_copy_buffer
GL_ARB_depth_clamp
GL_ARB_draw_buffers_blend
GL_ARB_draw_elements_base_vertex
GL_ARB_fragment_coord_conventions
GL_ARB_sample_shading
GL_ARB_seamless_cube_map
GL_ARB_shader_texture_lod
GL_ARB_sync
GL_ARB_texture_cube_map_array
GL_ARB_texture_gather
GL_ARB_texture_multisample
GL_ARB_texture_multisample_no_array
GL_ARB_uniform_buffer_object
GL_ATI_meminfo
GL_EXT_texture_storage
GL_NV_copy_image
GL_NV_texture_barrier
GL_AMD_blend_minmax_factor
GL_AMD_depth_clamp_separate
GL_AMD_sample_positions
GL_ARB_ES2_compatibility
GL_ARB_base_instance
GL_ARB_blend_func_extended
GL_ARB_debug_output
GL_ARB_draw_indirect
GL_ARB_explicit_attrib_location
GL_ARB_get_program_binary
GL_ARB_gpu_shader5
GL_ARB_gpu_shader_fp64
GL_ARB_occlusion_query2
GL_ARB_robustness
GL_ARB_sampler_objects
GL_ARB_separate_shader_objects
GL_ARB_shader_bit_encoding
GL_ARB_shader_precision
GL_ARB_shader_stencil_export
GL_ARB_shader_subroutine
GL_ARB_tessellation_shader
GL_ARB_texture_compression_bptc
GL_ARB_texture_rgb10_a2ui
GL_ARB_texture_swizzle
GL_ARB_timer_query
GL_ARB_transform_feedback2
GL_ARB_transform_feedback3
GL_ARB_vertex_attrib_64bit
GL_ARB_viewport_array
GL_EXT_shader_image_load_store
GL_EXT_texture_sRGB_decode
GL_AMD_bus_addressable_memory
GL_AMD_pinned_memory
GL_ARB_conservative_depth
GL_ARB_internalformat_query
GL_ARB_shader_atomic_counters
GL_ARB_shader_image_load_store
GL_ARB_shading_language_420pack
GL_ARB_texture_storage
GL_ARB_transform_feedback_instanced
GL_EXT_color_buffer_half_float
GL_EXT_debug_label
GL_AMD_shader_trinary_minmax
GL_ARB_ES3_compatibility
GL_ARB_arrays_of_arrays
GL_ARB_clear_buffer_object
GL_ARB_compute_shader
GL_ARB_copy_image
GL_ARB_explicit_uniform_location
GL_ARB_fragment_layer_viewport
GL_ARB_framebuffer_no_attachments
GL_ARB_internalformat_query2
GL_ARB_invalidate_subdata
GL_ARB_multi_draw_indirect
GL_ARB_program_interface_query
GL_ARB_shader_image_size
GL_ARB_shader_storage_buffer_object
GL_ARB_stencil_texturing
GL_ARB_texture_buffer_range
GL_ARB_texture_storage_multisample
GL_ARB_texture_view
GL_ARB_vertex_attrib_binding
GL_EXT_copy_buffer
GL_KHR_debug
GL_AMD_gpu_shader_half_float
GL_AMD_gpu_shader_int64
GL_ARB_bindless_texture
GL_ARB_buffer_storage
GL_ARB_clear_texture
GL_ARB_enhanced_layouts
GL_ARB_indirect_parameters
GL_ARB_multi_bind
GL_ARB_query_buffer_object
GL_ARB_shader_draw_parameters
GL_ARB_shader_group_vote
GL_ARB_sparse_texture
GL_ARB_texture_stencil8
GL_EXT_copy_image
GL_EXT_draw_buffers_indexed
GL_EXT_geometry_point_size
GL_EXT_gpu_shader5
GL_EXT_sRGB_write_control
GL_EXT_shader_integer_mix
GL_EXT_shader_io_blocks
GL_EXT_tessellation_point_size
GL_EXT_tessellation_shader
GL_EXT_texture_border_clamp
GL_EXT_texture_buffer
GL_EXT_timer_query
GL_KHR_blend_equation_advanced
GL_KHR_blend_equation_advanced_coherent
GL_NV_shader_atomic_int64
GL_ARB_clip_control
GL_ARB_conditional_render_inverted
GL_ARB_cull_distance
GL_ARB_derivative_control
GL_ARB_direct_state_access
GL_ARB_get_texture_sub_image
GL_ARB_pipeline_statistics_query
GL_ARB_shader_texture_image_samples
GL_ARB_sparse_buffer
GL_ARB_texture_barrier
GL_ARB_transform_feedback_overflow_query
GL_EXT_polygon_offset_clamp
GL_EXT_render_snorm
GL_KHR_robust_buffer_access_behavior
GL_KHR_robustness
GL_OVR_multiview
GL_ARB_gpu_shader_int64
GL_ARB_parallel_shader_compile
GL_ARB_shader_ballot
GL_ARB_shader_clock
GL_ARB_shader_viewport_layer_array
GL_ARB_texture_filter_minmax
GL_EXT_texture_sRGB_R8
GL_EXT_texture_sRGB_RG8
GL_KHR_no_error
GL_OVR_multiview2
GL_OVR_multiview_multisampled_render_to_texture
GL_ARB_gl_spirv
GL_EXT_clip_cull_distance
GL_EXT_memory_object
GL_EXT_memory_object_win32
GL_EXT_semaphore
GL_EXT_semaphore_win32
GL_AMD_gpu_shader_int16
GL_ARB_polygon_offset_clamp
GL_ARB_texture_filter_anisotropic
GL_EXT_texture_compression_bptc
GL_KHR_parallel_shader_compile

- Ready -
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on March 05, 2023, 02:46:49
The problem lies somewhere in the shader. When you comment the whole main() section of the fragment shader between the brackets and just use

FragColor=vec4(1.0,0.0,0.0,1.0);

as a single command in there, you'll get nice red models. Unless you start uncommenting the next lines in the main() section and reach this line:

vec2 uv = Vertex_UV;

It looks like the shader crashes (=turns black everything) when it retrieves the data from the Vertex Shader. I don't know why but this is the problem. I must check this further.
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on March 05, 2023, 14:08:17
It looks like AMD and Nvidia interpret the GL standard differently, AMD very strictly and Nvidia very casually. I was able to narrow down the problem in the vertex shader to the float array I use for the lights, but the fragment shader is giving me a headache.

I always cross-checked the shaders with the "glslangValidator" validator tool from the Khronos Group, which gave me no errors. But the ATI tools, well, hahaha, a whole bunch. Some errors I don't understand yet. So the best practice is to make  sure that these tools throw out no errors at all and the code must be tested on ATI, Intel and Nvidia.

KhronosGroup glslang tools (release and debug)
https://github.com/KhronosGroup/glslang/releases/tag/master-tot (https://github.com/KhronosGroup/glslang/releases/tag/master-tot)

Radeon GPU Analyzer (Radeon Developer Tool Suite, runs even if you dont have an ATI card)
https://gpuopen.com/rga/#download (https://gpuopen.com/rga/#download)

GPU ShaderAnalyzer (older variant of the GPU Analyzer above, but still works)
https://gpuopen.com/archived/gpu-shaderanalyzer/ (https://gpuopen.com/archived/gpu-shaderanalyzer/)
Title: Re: OpenB3D PBR Shader Demo
Post by: angros47 on March 05, 2023, 16:05:42
Quick question: is it possible that it's just related to the number of lights? The previous version for me didn't work because too many parameters were passed to the shader, by reducing the number of light I removed the error message

Also, try replacing "#version 130" with "#version 130 compatibility". On my OpenB3DPlus I had to add that in a shader to get it to work
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on March 05, 2023, 17:56:20
Quote from: angros47 on March 05, 2023, 16:05:42Quick question: is it possible that it's just related to the number of lights? The previous version for me didn't work because too many parameters were passed to the shader, by reducing the number of light I removed the error message

Also, try replacing "#version 130" with "#version 130 compatibility". On my OpenB3DPlus I had to add that in a shader to get it to work
WOW that is the solution! For an unknown reason, no more than 6 lights work on my AMD while 8 work on Nvidia. Even if the AMD hardware info responds with "8 lights", like the Nvidia does, too. But why is that?

So as a quick workaround, change in both shaders

#define NUM_LIGHTS 8 to "#define NUM_LIGHTS 6
Title: Re: OpenB3D PBR Shader Demo
Post by: angros47 on March 05, 2023, 18:12:24
It has to do with the limit of how many uniforms can be passed to a shader. Some video cards can accept more than others, but the Intel driver is a generic one, and it accepts the minimum, it seems. This is actually good for developing, because if a shader works on the Intel driver, it works everywhere
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on March 05, 2023, 18:41:09
What if I want to use more than 8 lights? Yes, I know there is a OpenGL Hardware lights limit of 8 but I could also pass - instead of the OpenGL light position/color data using the gl_LightSource function - the pivot and array data of virtual fake lights and process them in the shader. But when I can only pass 6 uniforms this is getting complex, maybe using two separate arrays for light 1-6 and light 7-12?

So 8 lights could still work with two arrays of 4
Title: Re: OpenB3D PBR Shader Demo
Post by: angros47 on March 05, 2023, 20:28:55
The earliest version of your shader worked with 8 lights, because each light used less parameters, and so it didn't reach the limit. Anyway, it is possible to use more than 8 lights, but you can't pass them with gl_LightSource. You must pass the light matrix as a generic matrix (it is what is done in OpenB3D ES version, have a look at the file "shaders.cpp" to see how it works): on GLES, or on WebGL, you have to do that to use the light, because the glLight command is not implemented.

OpenB3D actually does that to implement lights in the web version, but everything happens under the hood, as long as no custom shaders are used. Anyway, the limit is still 8 light: it could easily be increased, but it's not recommended, especially if you are going to use per fragment lightning: in fact, each single fragment would have light computed for each light source, and having too many light sources would slow down the rendering process.

Fixed function pipeline (or the default shader used in the Web version, that is made to work even on low end cards) uses per vertex lightning, that is less precise, but computes lights only for each vertex, so it's much faster.

To render many lights (10, 20 or more) usually deferred rendering is used: an example (experimental, and not very useful) is in OpenB3DPlus. It requires a custom shader that has to be used for every surface (it cannot be combined with other shaders), and then a postprocessing shader. It also requires more rendering targets: one for colors, one for depth map, one for normals. In the first pass, a special shader is used that renders all these parameters in the different targets, ignoring the light. In the second stages, these informations are used to render the final image, and lights are applied only on the fragments affected by them, not on every surface. This solution has some drawbacks, by the way, mainly the impossibility to render alpha blending correctly (since an alpha blended surface doesn't update the depth buffer or the normal buffer)
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on March 06, 2023, 01:35:11
I've digged a little more and adding these lines after calling the Graphics3D command gives interesting results:

Local VU:Int ; glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB, VarPtr VU)
Local FU:Int ; glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB, VarPtr FU)
Local VA:Int ; glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, VarPtr VA)
Local VS:String = String.FromCString(Byte Ptr(glGetString(GL_VERSION)))
Local SV:String = String.FromCString(Byte Ptr(glGetString(GL_SHADING_LANGUAGE_VERSION)))

Print "Max Vertex Uniforms..: " + VU
Print "Max Fragment Uniforms: " + FU
Print "Max Vertex Attributes: " + VA
Print "GL Version:..........: " + VS
Print "GLSL Version.........: " + SV
End

Nvidia (working)
Max Vertex Uniforms..: 4096
Max Fragment Uniforms: 4096
Max Vertex Attributes: 16
GL Version:..........: 4.6.0 NVIDIA 528.49
GLSL Version.........: 4.60 NVIDIA


ATI (not working, or with 6 lights only)
Max Vertex Uniforms..: 4000
Max Fragment Uniforms: 4000
Max Vertex Attributes: 32
GL Version:..........: 4.6.0 Compatibility Profile Context 22.20.42.221019
GLSL Version.........: 4.60


ATI has twice the Max. Vertex Attributes than Nvidia, but Nvidia works? huh?
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on March 06, 2023, 01:47:33
Oh, and a simple fix that all 8 lights work with little changes to the shader is to move the calculation of the Vertex_LightDir_Length from the Vertex Shader into the Fragment Shader to save some uniforms. I did the calculation in the Vertex Shader as it is not necessary to calculate the length for each texel, but well if it works let's move it back ::)

So the fixed Shaders are:

Vertex:
#version 130

#define NUM_LIGHTS 8

// ----------------------------------------------------------------------------

struct FloatArray {
float Float;
};

// ----------------------------------------------------------------------------

// input variables
uniform float ambFactor;
uniform FloatArray lightradius[NUM_LIGHTS];

// ----------------------------------------------------------------------------

// output variables to fragment shader
out vec2 Vertex_UV;
out vec4 Vertex_Color;
out vec4 Vertex_Position;
out vec3 Vertex_View_Vector;
out vec3 Vertex_Surface_to_Viewer_Direction;
out vec3 Vertex_Normal;
out vec3 Vertex_Ambient_Color;
out vec3 Vertex_LightDir[NUM_LIGHTS];
out vec3 Vertex_LightColor[NUM_LIGHTS];
out float Vertex_LightRange[NUM_LIGHTS];
//out float Vertex_LightDir_Length[NUM_LIGHTS];

// ----------------------------------------------------------------------------

void main()
{
Vertex_Color = gl_Color;
Vertex_Normal = normalize(gl_NormalMatrix * gl_Normal);
Vertex_Position = gl_ModelViewMatrix * gl_Vertex;

for (int i = 0; i < NUM_LIGHTS; ++i)
{
Vertex_LightDir[i] = gl_LightSource[i].position.xyz - Vertex_Position.xyz;
//Vertex_LightDir_Length[i] = length(normalize(Vertex_LightDir[i]));
Vertex_LightColor[i] = gl_LightSource[i].diffuse.rgb;
Vertex_LightRange[i] = lightradius[i].Float;
}

Vertex_UV = gl_MultiTexCoord0.xy;
Vertex_Ambient_Color = gl_LightModel.ambient.rgb * ambFactor;
Vertex_View_Vector = -Vertex_Position.xyz;

vec3 vViewModelPosition = vec3(gl_ModelViewMatrixInverse * vec4(0, 0, 0, 1.0));
vec3 vLocalSurfaceToViewerDirection = normalize(vViewModelPosition-gl_Vertex.xyz);
vec3 vvLocalSurfaceNormal = normalize(gl_Normal);

Vertex_Surface_to_Viewer_Direction = normalize(reflect(vLocalSurfaceToViewerDirection, vvLocalSurfaceNormal)) ;

gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}


Fragment:
#version 130

#define NUM_LIGHTS 8

// ----------------------------------------------------------------------------

struct FloatArray {
    float Float;
};

// ----------------------------------------------------------------------------

const float PI = 3.14159265359;
const float POscale = 0.05;
const float POmin = 8;
const float POmax = 32;
const float POmulti = 1.0;

// ----------------------------------------------------------------------------

uniform sampler2D albedoMap;
uniform sampler2D normalMap;
uniform sampler2D propMap;
uniform sampler2D metallicMap;
uniform sampler2D emissionMap;
uniform samplerCube envMap;

// ----------------------------------------------------------------------------

in vec2 Vertex_UV;
in vec2 Vertex_UVB;
in vec4 Vertex_Color;
in vec3 Vertex_Normal;
in vec3 Vertex_NormalCube;
in vec3 Vertex_Ambient_Color;
in vec3 Vertex_View_Vector;
in vec3 Vertex_Surface_to_Viewer_Direction;
in vec4 Vertex_Position;
in vec3 Vertex_LightDir[NUM_LIGHTS];
in vec3 Vertex_LightColor[NUM_LIGHTS];
in float Vertex_LightRange[NUM_LIGHTS];
//in float Vertex_LightDir_Length[NUM_LIGHTS];

out vec4 FragColor;

// ----------------------------------------------------------------------------

// fixed values
uniform float levelscale;
uniform float gamma;
uniform vec2 texscale;
uniform vec2 texoffset;
uniform vec3 envPos;

// variable values
uniform FloatArray lightradius[NUM_LIGHTS];

// ----------------------------------------------------------------------------

// variable Flags
uniform int flagPB;
uniform int flagPM;
uniform int flagEN;
uniform int flagEM;
uniform int setENV;
uniform int isMetal;

// ----------------------------------------------------------------------------

// texture existance flags
uniform int texAL;
uniform int texNM;
uniform int texPR;
uniform int texME;
uniform int texEM;

// ----------------------------------------------------------------------------

// light attenuation variables
uniform float A;
uniform float B;

// ----------------------------------------------------------------------------

mat3 cotangent_frame(vec3 N, vec3 p, vec2 uv)
{
vec3 dp1 = dFdx(p);
vec3 dp2 = dFdy(p);
vec2 duv1 = dFdx(uv);
vec2 duv = dFdy(uv);
 
vec3 dp2perp = cross(dp2, N);
vec3 dp1perp = cross(N, dp1);
vec3 T = dp2perp * duv1.x + dp1perp * duv.x;
vec3 B = dp2perp * duv1.y + dp1perp * duv.y;
 
float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
return mat3(T * invmax, B * invmax, N);
}

// ----------------------------------------------------------------------------

vec3 perturb_normal(vec3 N, vec3 V, vec3 map, vec2 texcoord)
{
map = map * 255.0 / 127.0 - 128.0 / 127.0;

mat3 TBN = cotangent_frame(N, -V, texcoord);
return normalize(TBN * map);
}

// ----------------------------------------------------------------------------

vec3 ToneMapPBR(vec3 color)
{
// HDR tonemapping and gamma correction
color = color / (color + vec3(1.0));
color = pow(color, vec3(1.0 / gamma));

return color;
}

// ----------------------------------------------------------------------------

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 NdotH2 = NdotH * NdotH;

float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;

return nom / denom;
}

// ----------------------------------------------------------------------------

float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r * r) / 8.0;

float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;

return nom / denom;
}

// ----------------------------------------------------------------------------

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 ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);

return ggx1 * ggx2;
}

// ----------------------------------------------------------------------------

vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
if(cosTheta > 1.0)
cosTheta = 1.0;
float p = pow(1.0 - cosTheta,5.0);
return F0 + (1.0 - F0) * p;
}

// ----------------------------------------------------------------------------

float CalcAtt(float distance, float range, float a, float b)
{
return 1.0 / (1.0 + a * distance + b * distance * distance);
}

// ----------------------------------------------------------------------------

// Contrast Matrix
mat4 contrastMatrix(float contrast)
{
float t = ( 1.0 - contrast ) / 2.0;
   
return mat4(contrast, 0, 0, 0,
0, contrast, 0, 0,
0, 0, contrast, 0,
t, t, t, 1);

}

// ----------------------------------------------------------------------------

vec2 ParallaxOcclusionMapping(vec2 texCoords, vec3 viewDir)
{
    // number of depth layers
    float numLayers = mix(POmax, POmin, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));

    // calculate the size of each layer
    float layerDepth = 1.0 / numLayers;

    // depth of current layer
    float currentLayerDepth = 0.0;

    // the amount to shift the texture coordinates per layer (from vector P)
    vec2 P = viewDir.xy / viewDir.z * POscale * POmulti;
    vec2 deltaTexCoords = P / numLayers;

    // get initial values
    vec2  currentTexCoords     = texCoords;
    float currentDepthMapValue = texture(propMap, currentTexCoords).b;

    while(currentLayerDepth < currentDepthMapValue)
    {
        // shift texture coordinates along direction of P
        currentTexCoords -= deltaTexCoords;

        // get depthmap value at current texture coordinates
        currentDepthMapValue = texture(propMap, currentTexCoords).b;

        // get depth of next layer
        currentLayerDepth += layerDepth;
    }

    // get texture coordinates before collision (reverse operations)
    vec2 prevTexCoords = currentTexCoords + deltaTexCoords;

    // get depth after and before collision for linear interpolation
    float afterDepth  = currentDepthMapValue - currentLayerDepth;
    float beforeDepth = texture(propMap, prevTexCoords).b - currentLayerDepth + layerDepth;

    // interpolation of texture coordinates
    float weight = afterDepth / (afterDepth - beforeDepth);
    vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);

    return finalTexCoords;
}

mat3 computeTBN(vec2 tempUv, vec3 worldPos, vec3 worldNormal){
    vec3 Q1  = dFdx(worldPos);
    vec3 Q2  = dFdy(worldPos);
    vec2 st1 = dFdx(tempUv);
    vec2 st2 = dFdy(tempUv);

    vec3 n   = normalize(worldNormal);
    vec3 t  = normalize(Q1*st2.t - Q2*st1.t);

    // or directly compute from the equation
    vec3 b = normalize(-Q1*st2.s + Q2*st1.s);

    mat3 tbn = mat3(t, b, n);

    return tbn;
}

// ----------------------------------------------------------------------------

void main()
{
// Texture coordinates
vec2 ts=texscale;
vec2 uv = Vertex_UV;
//ts.y=-ts.y;
uv = (uv * ts) + texoffset;

// TBN Matrix
vec3 VN = normalize(Vertex_Normal);
vec3 VV = Vertex_View_Vector;
//mat3 TBN = computeTBN(uv.st,-VV,Vertex_Normal);
mat3 TBN = cotangent_frame(VN, VV, uv.st);

// Parallax Mapping
if(flagPM > 0)
{
uv = ParallaxOcclusionMapping(uv, normalize(-VV * TBN));

}

// Albedo Texture
vec4 albedo = vec4(0.5, 0.5, 0.5, 1.0);
if(texAL > 0)
{
albedo = texture(albedoMap, uv);
albedo.rgb = pow(albedo.rgb, vec3(2.2));
}

// Normalmap Texture
vec3 nrm = Vertex_Normal;
if(texNM > 0)
{
nrm = texture(normalMap, uv).rgb;
//nrm.y=1.0-nrm.y;
}

// 3. Perturbated Normals
vec3 N = Vertex_Normal.xyz;
vec3 V = normalize(Vertex_View_Vector);
vec3 PN;
vec3 PNC;
if(texNM == 0)
{
PN = N;
nrm = vec3(0.0, 0.0, 1.0);
}
else
{
vec3 VN = normalize(N);
vec3 VV = normalize(Vertex_View_Vector);
//mat3 TBN = cotangent_frame(VN, -VV, uv.st);

PN = perturb_normal(VN, VV, nrm, uv);
PNC = perturb_normal(Vertex_NormalCube, VV, nrm, uv);
}

// PBR Texture
float ao = 1.0;
float roughness = 0.5;
float metallic = 0.5;

// Roughness
if(texPR > 0)
{
vec3 pbr = texture(propMap, uv).rgb;

ao = pbr.r;
roughness = pbr.g;
}

// Metallic
if(texME > 0)
{
metallic = texture(metallicMap, uv).r;
}

// Emissive
vec3 emission = vec3(0.0);
if(texEM > 0){emission = texture(emissionMap, uv).rgb * 2.0;}

// Ambient
vec3 ambient = Vertex_Ambient_Color * albedo.rgb;

// PBR Lighting
vec3 Lo = vec3(0.0);
vec3 irradiance;
vec3 diffuse=albedo.rgb;
vec3 reflectDir;

// Reflection
vec3 NormalizedRSTVD = normalize(Vertex_Surface_to_Viewer_Direction);

if(flagPB > 0)
{
// calculate reflectance at normal incidence; if dia-electric (like plastic) use F0
// of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)   
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo.rgb, metallic);

for(int i = 0; i < NUM_LIGHTS; ++i)
{
// calculate per-light radiance
vec3 L = normalize(Vertex_LightDir[i]);
vec3 H = normalize(V + L);
float distance = length(normalize(Vertex_LightDir[i]));
float attenuation = CalcAtt(distance, Vertex_LightRange[i], A, B);
vec3 radiance = Vertex_LightColor[i] * attenuation;

// Cook-Torrance BRDF
float NDF = DistributionGGX(PN, H, roughness);
float G = GeometrySmith(PN, V, L, roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
          
// specularity
vec3 nominator = NDF * G * F;
float denominator = 4.0 * max(dot(PN, V), 0.0) * max(dot(PN, L), 0.0) + 0.001;
vec3 specular = nominator / denominator;

// kS is equal to Fresnel
vec3 kS = F;

// for energy conservation, the diffuse and specular light can't
// be above 1.0 (unless the surface emits light); to preserve this
// relationship the diffuse component (kD) should equal 1.0 - kS.
vec3 kD = vec3(1.0) - kS;

// multiply kD by the inverse metalness such that only non-metals
// have diffuse lighting, or a linear blend if partly metal (pure metals
// have no diffuse light).
kD *= 1.0-metallic;

// Metallic Reflection --------------------------------------------

diffuse=albedo.rgb;

if(isMetal==1 && flagEN==1 && setENV==1)
{
vec3 v=NormalizedRSTVD;
irradiance = textureCube(envMap, vec3(-v.x,v.y,-v.z)).rgb;
diffuse = (albedo.rgb * (0.0 + (irradiance * 1.0)));
}

// check backface lighting
float NdotL = max(dot(PN, L), 0.0);

Lo += (kD * diffuse.rgb / PI + (specular)) * Vertex_LightRange[i] * radiance * NdotL;
}
}
// PBR off
else
{
for(int i = 0; i < NUM_LIGHTS; ++i)
{
float distance = length(normalize(Vertex_LightDir[i]));
float attenuation = CalcAtt(distance, Vertex_LightRange[i], A, B);
vec3 radiance = Vertex_LightColor[i] * attenuation;

vec3 L = normalize(Vertex_LightDir[i]);
vec3 N = Vertex_Normal.xyz;

float NdotL = max(dot(N, L), 0.0);
Lo += (diffuse.rgb / PI) * Vertex_LightRange[i] * radiance * NdotL;
}
}

// mix final lighting with ambient
vec3 color=(Lo+ambient);

// Ambient Occlusion
color *= ao;

// more contrast using normals and contrast matrix
float NL = max(dot(PN, normalize(Vertex_View_Vector)), 0.0);
color*=NL;
color.rgb*=(vec4(1.0) * contrastMatrix(2.0)).rgb;

// Tonemapping
color = ToneMapPBR(color);

// Gamma correction
color = pow(color, vec3(1.0/2.2));

if(flagEM==0){emission=vec3(0.0);}

// Final olor plus Emissive with Alpha
FragColor = vec4(color+emission, albedo.a);

}
Title: Re: OpenB3D PBR Shader Demo
Post by: markcwm on March 06, 2023, 18:29:10
I have a fix for the Intel bug now, it takes a max of 7 lights and there is one line in the frag that makes it all go black.

PNC = perturb_normal(Vertex_NormalCube, VV, nrm, uv);
// there is no definition for Vertex_NormalCube so changed it to:
PNC = perturb_normal(Vertex_Normal, VV, nrm, uv);

There is still a strange flickering edges issue in the POM and when I turn off PBR in the main example. Some screenies. 

(https://i.imgur.com/ECWuQRU.png)
(https://i.imgur.com/6385iUt.png)
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on March 08, 2023, 00:21:27
The strange "flickering" is because of the precision loss and because it is not antialiased. Try 256 or 512 as POM values instead of 8...32. POM is also not suitable for non-flat surfaces, maybe for ground or a straight wall to give it more detail.

Anyway, I've completely revised the shaders, cleaned them of unused variables and moved some stuff from the Vertex to the Fragment Shader which had almost no FPS impact but makes the code much cleaner. I've also got rid of the strange gamma correction I've used and added different Tonemap models again to switch between. The surfaces now look more realistic, maybe also a little darker. I also changed the POM test texture to show the effect even better and animated it and added a FPS counter. Tested on my Nvidia and Ati.

Again, the download link: ShaderExample.zip (https://www.christianhart.de/bmax/ShaderExample.zip)

ShaderExample.jpg ShaderExample_minimum.jpg ShaderExample_POM.jpg


Title: Re: OpenB3D PBR Shader Demo
Post by: markcwm on March 09, 2023, 23:45:32
Wow, that works perfectly now on my Intel card. Whatever you did it works, and it runs with 8 lights and no flickering edges at 8 to 32 POM min max range. Some screenies.
 
(https://i.imgur.com/zXkoGbm.png)
(https://i.imgur.com/CMwvTBo.png)
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on March 10, 2023, 00:33:30
Good to hear. Now we need self-shadowing and biased POM. I still have no clue how to implement this but I may find this out, too  ;D
Title: Re: OpenB3D PBR Shader Demo
Post by: angros47 on March 10, 2023, 16:20:30
Self-shadowing is already implemented, in OpenB3D. And, since it is implemented through stencils, and not through a specialized shader, it could be applied on any mesh, including the ones using PBR shader
Title: Re: OpenB3D PBR Shader Demo
Post by: angros47 on March 26, 2023, 22:38:46
struct PBRTexture {
    Texture* basecolor;    //Base Color: RGBA, RGB=color and A=Transparency
    Texture* normals;    //Normals (DirectX): RGBA, RGB=XYZ and A=Bumpmap
    Texture* PBR;        /*PBR Combotexture: RGB
                        Channel Red = Ambient Occlusion
                Channel Green = Roughness
                Channel Blue = Metallic*/
    Texture* EMI;        //Emissive: RGB
};

extern "C" PBRTexture* CreatePBRTexture(int width, int height, unsigned char* BaseColor, unsigned char* Normals, unsigned char* Heights, unsigned char* Roughness, unsigned char* Metallic, unsigned char* AO, unsigned char* Emissive){
    PBRTexture* tex=new PBRTexture;

    tex->basecolor=CreateTexture(width,height,2,0);
    BufferToTex(tex->basecolor,BaseColor,0);

    unsigned char *src1, *src2, *src3, *dst;

    unsigned char* normals_buffer=new unsigned char[width*height*4];
    src1=Normals; src2= Heights; dst=normals_buffer;
    for (int y=0; y<height; y++){
        for (int x=0; x<width; x++){
            if (src1!=0){
                *dst=*src1; dst++; src1++;    //Normal X
                *dst=*src1; dst++; src1++;    //Normal Y
                *dst=*src1; dst++; src1++;    //Normal Z
            }else{
                *dst=0; dst++;    //Normal X
                *dst=0; dst++;    //Normal Y
                *dst=0; dst++;    //Normal Z
            }
            if (src2!=0){
                *dst=*src2; dst++; src2++;    //Height map
            }else{
                *dst=0; dst++;    //Height map
            }
        }
    }
    tex->normals=CreateTexture(width,height,2,0);
    BufferToTex(tex->normals,normals_buffer,0);


    unsigned char* PBR_buffer=new unsigned char[width*height*4];
    src1=AO; src2= Roughness; src3= Metallic; dst=PBR_buffer;
    for (int y=0; y<height; y++){
        for (int x=0; x<width; x++){
            if (src1!=0){
                *dst=*src1; dst++; src1++;    //Ambient Occlusion
            }else{
                *dst=0; dst++;    //Ambient Occlusion
            }
            if (src2!=0){
                *dst=*src2; dst++; src2++;    //Roughness
            }else{
                *dst=0; dst++;    //Roughness
            }
            if (src3!=0){
                *dst=*src3; dst++; src3++;    //Metallic
            }else{
                *dst=0; dst++;    //Metallic
            }
            *dst=255; dst++;        //unused
        }
    }
    tex->PBR=CreateTexture(width,height,2,0);
    BufferToTex(tex->PBR,PBR_buffer,0);

    tex->EMI=CreateTexture(width,height,2,0);
    if (Emissive!=0){
        BufferToTex(tex->EMI,Emissive,0);
    }

    return tex;
}

extern "C" PBRTexture* LoadPBRTexture(char* BaseColor, char* Normals, char* Heights,  char* Roughness, char* Metallic, char* AO, char* Emissive){
    int width, height, w, h;

    unsigned char* color_buffer;
    if (BaseColor!=0){
        color_buffer=stbi_load(BaseColor ,&width,&height,0,4);
    }else{
        return 0;
    }
   
    unsigned char* normal_buffer;
    if (Normals!=0){
        normal_buffer=stbi_load(Normals ,&w,&h,0,3);
    }else{
        normal_buffer=0;
    }

    unsigned char* height_buffer;
    if (Heights!=0){
        height_buffer=stbi_load(Heights ,&w,&h,0,1);
    }else{
        height_buffer=0;
    }

    unsigned char* roughness_buffer;
    if (Roughness!=0){
        roughness_buffer=stbi_load(Roughness ,&w,&h,0,1);
    }else{
        roughness_buffer=0;
    }

    unsigned char* metallic_buffer;
    if (Metallic!=0){
        metallic_buffer=stbi_load(Metallic ,&w,&h,0,1);
    }else{
        metallic_buffer=0;
    }

    unsigned char* ao_buffer;
    if (AO!=0){
        ao_buffer=stbi_load(AO ,&w,&h,0,1);
    }else{
        ao_buffer=0;
    }

    unsigned char* emissive_buffer;
    if (Emissive!=0){
        emissive_buffer=stbi_load(Emissive ,&w,&h,0,4);
    }else{
        emissive_buffer=0;
    }

    PBRTexture* tex = CreatePBRTexture(width, height, color_buffer, normal_buffer, height_buffer, roughness_buffer, metallic_buffer, ao_buffer, emissive_buffer);

    if (color_buffer!=0) delete color_buffer;
    if (normal_buffer!=0) delete normal_buffer;
    if (height_buffer!=0) delete height_buffer;
    if (roughness_buffer!=0) delete roughness_buffer;
    if (metallic_buffer!=0) delete metallic_buffer;
    if (ao_buffer!=0) delete ao_buffer;
    if (emissive_buffer!=0) delete emissive_buffer;

    return tex;

}

A simple loader in C++ (it uses the function stbi_load that is part of OpenB3D, and the function CreateTexture) that converts the set of images of a PBR texture (usually, they are provided as separate images) into a set of combined-channel images suitable to work with this shader
Title: Re: OpenB3D PBR Shader Demo
Post by: angros47 on April 26, 2023, 00:55:18
#include <iostream>
/*
License Agreement
---------------------------------------------------------------------------

Copyright (c) 2023, Christian Hart. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

- Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION). HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/

#include "OpenB3D.h"
#include "stb_image.h"

const char *pbr_vert_shader=
"#version 130\r\n"

"// output variables to fragment shader\r\n"
"out vec2 Vertex_UV;"
"out vec4 Vertex_Color;"
"out vec3 Vertex_Normal;"
"out vec4 Vertex_Position;"
"out vec3 Vertex_Surface_to_Viewer_Direction;"

"// ----------------------------------------------------------------------------\r\n"

"void main()"
"{"
" Vertex_UV = gl_MultiTexCoord0.xy;"
" Vertex_Color = gl_Color;"
" Vertex_Normal = normalize(gl_NormalMatrix * gl_Normal);"
" Vertex_Position = gl_ModelViewMatrix * gl_Vertex;"

" vec3 vViewModelPosition = vec3(gl_ModelViewMatrixInverse * vec4(0, 0, 0, 1.0));"
" vec3 vLocalSurfaceToViewerDirection = normalize(vViewModelPosition-gl_Vertex.xyz);"
" vec3 vvLocalSurfaceNormal = normalize(gl_Normal);"

" Vertex_Surface_to_Viewer_Direction = normalize(reflect(vLocalSurfaceToViewerDirection, vvLocalSurfaceNormal)) ;"

" gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;"
"}";


const char *pbr_frag_shader=
"#version 130\r\n"

"#define NUM_LIGHTS 8\r\n"

"// ----------------------------------------------------------------------------\r\n"

"struct FloatArray {"
"    float Float;"
"};"

"const float PI = 3.14159265359;"

"// ----------------------------------------------------------------------------\r\n"

"// Parallax Occlusion values\r\n"
"const float POscale = 0.04;"
"const float POmin = 8;"
"const float POmax = 32;"

"// ----------------------------------------------------------------------------\r\n"

"// from the Vertex Shader\r\n"
"in vec2 Vertex_UV;"
"in vec4 Vertex_Color;"
"in vec3 Vertex_Normal;"
"in vec4 Vertex_Position;"
"in vec3 Vertex_Surface_to_Viewer_Direction;"

"// ----------------------------------------------------------------------------\r\n"

"out vec4 FragColor;"

"// ----------------------------------------------------------------------------\r\n"

"// variable inputs\r\n"
"uniform float levelscale;   // mesh scales \r\n"
"uniform float gamma;        // user gamma correction \r\n"
"uniform float POmulti;      // Parallax Occlusion multiplicator \r\n"
"uniform vec2 texscale;      // texture scale (0...x) \r\n"
"uniform vec2 texoffset;     // texture offset (0...x) \r\n"
"uniform int timer;          // timing \r\n"

"// ---------------------------------------------------------------------------- \r\n"

"// textures \r\n"
"uniform sampler2D albedoMap;"
"uniform sampler2D normalMap;"
"uniform sampler2D propMap;"
"uniform sampler2D emissionMap;"
"uniform samplerCube envMap;"

"// ---------------------------------------------------------------------------- \r\n"

"// variable Flags \r\n"
"uniform int flagPB;"
"uniform int flagPM;"
"uniform int flagEN;"
"uniform int flagEM;"
"uniform int flagTM;"
"uniform int setENV;"
"uniform int isMetal;"

"// ---------------------------------------------------------------------------- \r\n"

"// texture existance flags \r\n"
"uniform int texAL;"
"uniform int texNM;"
"uniform int texPR;"
"uniform int texEM;"

"// ---------------------------------------------------------------------------- \r\n"

"// light related variables \r\n"
"uniform float A;"
"uniform float B;"
"uniform FloatArray lightradius[NUM_LIGHTS];"

"// ---------------------------------------------------------------------------- \r\n"

"mat3 cotangent_frame(vec3 N, vec3 p, vec2 uv)"
"{"
" vec3 dp1 = dFdx(p);"
" vec3 dp2 = dFdy(p);"
" vec2 duv1 = dFdx(uv);"
" vec2 duv = dFdy(uv);"
 
" vec3 dp2perp = cross(dp2, N);"
" vec3 dp1perp = cross(N, dp1);"
" vec3 T = dp2perp * duv1.x + dp1perp * duv.x;"
" vec3 B = dp2perp * duv1.y + dp1perp * duv.y;"
 
" float invmax = inversesqrt(max(dot(T, T), dot(B, B)));"
" return mat3(T * invmax, B * invmax, N);"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"vec3 perturb_normal(vec3 N, vec3 V, vec3 map, vec2 texcoord)"
"{"
" map = map * 255.0 / 127.0 - 128.0 / 127.0;"

" mat3 TBN = cotangent_frame(N, -V, texcoord);"
" return normalize(TBN * map);"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"vec3 ToneMapPBR(vec3 color)"
"{"
" // HDR tonemapping and gamma correction \r\n"
" color = color / (color + vec3(1.0));"
" color = pow(color, vec3(1.0 / gamma));"

" return color;"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"vec3 Uncharted(vec3 x)"
"{"
" float A = 0.15;"
" float B = 0.50;"
" float C = 0.10;"
" float D = 0.20;"
" float E = 0.02;"
" float F = 0.30;"

" return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"vec3 ToneMapUncharted(vec3 color)"
"{"
" color = Uncharted(color * 4.5) * (1.0 / Uncharted(vec3(11.2)));"
" color = pow(color, vec3(1.0 / gamma));"
" return color;"

"}"

"// ---------------------------------------------------------------------------- \r\n"

"vec3 ToneMapSCurve(vec3 x)"
"{"
" float a = 2.51f;"
" float b = 0.03f;"
" float c = 2.43f;"
" float d = 0.59f;"
" float e = 0.14f;"
" return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"vec3 ToneMapFilmic(vec3 color)"
"{"
" vec4 vh = vec4(color*0.5, gamma);"
" vec4 va = 1.425 * vh + 0.05;"
" vec4 vf = (vh * va + 0.004) / (vh * (va + 0.55) + 0.0491) - 0.0821;"
" return vf.rgb / vf.www;"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"vec3 ToneMapExposure(vec3 color)"
"{"
" color = exp(-1.0 / ( 2.72 * color + 0.15 ));"
" color = pow(color, vec3(1. / gamma));"
" return color;"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"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 NdotH2 = NdotH * NdotH;"

" float nom = a2;"
" float denom = (NdotH2 * (a2 - 1.0) + 1.0);"
" denom = PI * denom * denom;"

" return nom / denom;"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"float GeometrySchlickGGX(float NdotV, float roughness)"
"{"
" float r = (roughness + 1.0);"
" float k = (r * r) / 8.0;"

" float nom = NdotV;"
" float denom = NdotV * (1.0 - k) + k;"

" return nom / denom;"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"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 ggx2 = GeometrySchlickGGX(NdotV, roughness);"
" float ggx1 = GeometrySchlickGGX(NdotL, roughness);"

" return ggx1 * ggx2;"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"vec3 fresnelSchlick(float cosTheta, vec3 F0)"
"{"
" if(cosTheta > 1.0)"
" cosTheta = 1.0;"
" float p = pow(1.0 - cosTheta,5.0);"
" return F0 + (1.0 - F0) * p;"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"float CalcAtt(float distance, float range, float a, float b)"
"{"
" return 1.0 / (1.0 + a * distance + b * distance * distance);"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"vec2 ParallaxOcclusionMapping(vec2 texCoords, vec3 viewDir)"
"{"
"    // number of depth layers \r\n"
"    float numLayers = mix(POmax, POmin, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));"

"    // calculate the size of each layer \r\n"
"    float layerDepth = 1.0 / numLayers;"

"    // depth of current layer \r\n"
"    float currentLayerDepth = 0.0;"

"    // the amount to shift the texture coordinates per layer (from vector P) \r\n"
"    vec2 P = viewDir.xy / viewDir.z * POscale * POmulti;"
"    vec2 deltaTexCoords = P / numLayers;"

"    // get initial values \r\n"
"    vec2  currentTexCoords     = texCoords;"
"    float currentDepthMapValue = texture(normalMap, currentTexCoords).a;"

"    while(currentLayerDepth < currentDepthMapValue)"
"    {"
"        // shift texture coordinates along direction of P \r\n"
"        currentTexCoords -= deltaTexCoords;"

"        // get depthmap value at current texture coordinates \r\n"
"        currentDepthMapValue = texture(normalMap, currentTexCoords).a;"

"        // get depth of next layer \r\n"
"        currentLayerDepth += layerDepth; \r\n"
"    }"

"    // get texture coordinates before collision (reverse operations) \r\n"
"    vec2 prevTexCoords = currentTexCoords + deltaTexCoords;"

"    // get depth after and before collision for linear interpolation \r\n"
"    float afterDepth  = currentDepthMapValue - currentLayerDepth;"
"    float beforeDepth = texture(normalMap, prevTexCoords).a - currentLayerDepth + layerDepth;"

"    // interpolation of texture coordinates \r\n"
"    float weight = afterDepth / (afterDepth - beforeDepth);"
"    vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);"

"    return finalTexCoords;"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"mat3 computeTBN(vec2 tempUv, vec3 worldPos, vec3 worldNormal)"
"{"

"    vec3 Q1  = dFdx(worldPos);"
"    vec3 Q2  = dFdy(worldPos);"
"    vec2 st1 = dFdx(tempUv);"
"    vec2 st2 = dFdy(tempUv);"

" // normal and tangent \r\n"
"    vec3 n   = normalize(worldNormal);"
"    vec3 t  = normalize(Q1*st2.t - Q2*st1.t);"

"    // bitangent \r\n"
"    vec3 b = normalize(-Q1*st2.s + Q2*st1.s);"

"    return mat3(t, b, n);"
"}"

"// ---------------------------------------------------------------------------- \r\n"

"void main()"
"{"
" // Texture coordinates \r\n"
" vec2 ts=texscale;"
" vec2 uv = Vertex_UV;"
" uv = (uv * ts) + texoffset;"

" // TBN Matrix \r\n"
" vec3 VV = -Vertex_Position.xyz;"
" vec3 VVN = normalize(VV);"
" vec3 N = Vertex_Normal.xyz;"
" vec3 VN = normalize(Vertex_Normal);"
" mat3 TBN = computeTBN(uv.st,-VV,Vertex_Normal);"

" // Parallax Occlusion Mapping \r\n"
" if(flagPM > 0)"
" {"
" uv = ParallaxOcclusionMapping(uv, normalize(-VV * TBN));"
" } "

" // Albedo Texture (sRGB, with gamma correction) \r\n"
" vec4 albedo = vec4(0.5, 0.5, 0.5, 1.0);"
" if(texAL > 0)"
" {"
" albedo = texture(albedoMap, uv);"
" albedo.rgb = pow(albedo.rgb, vec3(2.2));"
" }"

" // Normalmap Texture \r\n"
" vec3 nrm = Vertex_Normal;"
" if(texNM > 0)"
" {"
" nrm = texture(normalMap, uv).rgb;"
" }"

" // 3. Perturbated Normals \r\n"
" vec3 PN = N;"
" if(texNM > 0)"
" {"
" PN = perturb_normal(VN, VVN, nrm, uv);"
" } "

" // PBR Texture \r\n"
" float ao = 1.0;"
" float roughness = 0.5;"
" float metallic = 0.5;"
" if(texPR > 0)"
" {"
" vec3 pbr = texture(propMap, uv).rgb;"

" ao = pbr.r;"
" roughness = pbr.g;"
" metallic = pbr.b;"
" }"

" // Emissive \r\n"
" vec3 emission = vec3(0.0);"
" if(texEM > 0 && flagEM > 0)"
" {"
" emission = texture(emissionMap, uv).rgb * (1.0+cos(timer/30.0));"
" }"

" // Ambient \r\n"
" vec3 ambient = gl_LightModel.ambient.rgb * albedo.rgb;"

" // PBR Lighting \r\n"
" vec3 Lo = vec3(0.0);"
" vec3 irradiance;"
" vec3 diffuse=albedo.rgb;"

" // Reflection \r\n"
" vec3 NormalizedRSTVD = normalize(Vertex_Surface_to_Viewer_Direction);"

" if(flagPB > 0)"
" {"
" // calculate reflectance at normal incidence; if dia-electric (like plastic) use F0  \r\n"
" // of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)     \r\n"
" vec3 F0 = vec3(0.04);"
" F0 = mix(F0, albedo.rgb, metallic);"

" for(int i = 0; i < NUM_LIGHTS; ++i) "
" {"
" // calculate per-light radiance \r\n"
" vec3 L = normalize(gl_LightSource[i].position.xyz - Vertex_Position.xyz);"
" vec3 H = normalize(VVN + L);"

" float distance = length(L);"
" float attenuation = CalcAtt(distance, lightradius[i].Float, A, B);"
" vec3 radiance = gl_LightSource[i].diffuse.rgb * attenuation;"

" // Cook-Torrance BRDF \r\n"
" float NDF = DistributionGGX(PN, H, roughness);"
" float G = GeometrySmith(PN, VVN, L, roughness);"
" vec3 F = fresnelSchlick(max(dot(H, VVN), 0.0), F0);"
          
" // specularity \r\n"
" vec3 nominator = NDF * G * F;"
" float denominator = 4.0 * max(dot(PN, VVN), 0.0) * max(dot(PN, L), 0.0) + 0.001;"
" vec3 specular = nominator / denominator;"

" // kS is equal to Fresnel \r\n"
" vec3 kS = F;"

" // for energy conservation, the diffuse and specular light can't \r\n"
" // be above 1.0 (unless the surface emits light); to preserve this \r\n"
" // relationship the diffuse component (kD) should equal 1.0 - kS. \r\n"
" vec3 kD = vec3(1.0) - kS;"

" // multiply kD by the inverse metalness such that only non-metals  \r\n"
" // have diffuse lighting, or a linear blend if partly metal (pure metals \r\n"
" // have no diffuse light). \r\n"
" kD *= 1.0-metallic;"

" // Metallic Reflection \r\n"
" if(isMetal==1 && flagEN==1 && setENV==1)"
" {"
" vec3 v=NormalizedRSTVD;"
" irradiance = textureCube(envMap, vec3(-v.x,v.y,-v.z)).rgb;"
" diffuse = (albedo.rgb * (0.0 + (irradiance * 1.0)));"
" }"

" // check backface lighting \r\n"
" float NdotL = max(dot(PN, L), 0.0);"

" // sum all together:  \r\n"
" Lo += (kD * diffuse.rgb / PI + specular) * lightradius[i].Float * radiance * NdotL;"
" }"
" }"
" // PBR off \r\n"
" else"
" {"
" for(int i = 0; i < NUM_LIGHTS; ++i) "
" {"
" vec3 L = normalize(gl_LightSource[i].position.xyz - Vertex_Position.xyz);"
" //vec3 N = Vertex_Normal.xyz; \r\n"

" float distance = length(L);"
" float attenuation = CalcAtt(distance, lightradius[i].Float, A, B);"
" vec3 radiance = gl_LightSource[i].diffuse.rgb * attenuation; "

" float NdotL = max(dot(N, L), 0.0);"
" Lo += (diffuse.rgb / PI) * lightradius[i].Float * radiance * NdotL;"
" }"
" }"

" // mix final lighting with ambient \r\n"
" vec3 color=(Lo+ambient);"

" // Ambient Occlusion \r\n"
" color *= ao;"

" // Tonemapping \r\n"
" if(flagTM == 1){color = ToneMapPBR(color);}"
" if(flagTM == 2){color = ToneMapExposure(color);}"
" if(flagTM == 3){color = ToneMapSCurve(color);}"
" if(flagTM == 4){color = ToneMapUncharted(color);}"
" if(flagTM == 5){color = ToneMapFilmic(color);}"

" // Final olor plus Emissive with Alpha \r\n"
" FragColor = vec4(color+emission, albedo.a);"

"}";

struct PBRTexture {
Texture* basecolor; //Base Color: RGBA, RGB=color and A=Transparency
Texture* normals; //Normals (DirectX): RGBA, RGB=XYZ and A=Bumpmap
Texture* PBR; /*PBR Combotexture: RGB
                 Channel Red = Ambient Occlusion
Channel Green = Roughness
Channel Blue = Metallic*/
Texture* EMI; //Emissive: RGB
};

Texture* ENV=0; //Evironment cubemap

extern "C" void AmbientCubeTexture(Texture* tex){
ENV=tex;
}

extern "C" PBRTexture* CreatePBRTexture(int width, int height, unsigned char* BaseColor, unsigned char* Normals, unsigned char* Heights, unsigned char* Roughness, unsigned char* Metallic, unsigned char* AO, unsigned char* Emissive){
PBRTexture* tex=new PBRTexture;

tex->basecolor=CreateTexture(width,height,2,0);
BufferToTex(tex->basecolor,BaseColor,0);

unsigned char *src1, *src2, *src3, *dst;

unsigned char* normals_buffer=new unsigned char[width*height*4];
src1=Normals; src2= Heights; dst=normals_buffer;
for (int y=0; y<height; y++){
for (int x=0; x<width; x++){
if (src1!=0){
*dst=*src1; dst++; src1++; //Normal X
*dst=255-*src1; dst++; src1++; //Normal Y, OpenGL convention
*dst=*src1; dst++; src1++; //Normal Z
}else{
*dst=0; dst++; //Normal X
*dst=0; dst++; //Normal Y
*dst=0; dst++; //Normal Z
}
if (src2!=0){
*dst=*src2; dst++; src2++; //Height map
}else{
*dst=0; dst++; //Height map
}
}
}
tex->normals=CreateTexture(width,height,2,0);
BufferToTex(tex->normals,normals_buffer,0);


unsigned char* PBR_buffer=new unsigned char[width*height*4];
src1=AO; src2= Roughness; src3= Metallic; dst=PBR_buffer;
for (int y=0; y<height; y++){
for (int x=0; x<width; x++){
if (src1!=0){
*dst=*src1; dst++; src1++; //Ambient Occlusion
}else{
*dst=0; dst++; //Ambient Occlusion
}
if (src2!=0){
*dst=*src2; dst++; src2++; //Roughness
}else{
*dst=0; dst++; //Roughness
}
if (src3!=0){
*dst=*src3; dst++; src3++; //Metallic
}else{
*dst=0; dst++; //Metallic
}
*dst=255; dst++; //unused
}
}
tex->PBR=CreateTexture(width,height,2,0);
BufferToTex(tex->PBR,PBR_buffer,0);

tex->EMI=CreateTexture(width,height,2,0);
if (Emissive!=0){
BufferToTex(tex->EMI,Emissive,0);
}

return tex;
}

extern "C" PBRTexture* LoadPBRTexture(char* BaseColor, char* Normals, char* Heights,  char* Roughness, char* Metallic, char* AO, char* Emissive){
int width, height, w, h;

unsigned char* color_buffer;
if (BaseColor!=0){
color_buffer=stbi_load(BaseColor ,&width,&height,0,4);
}else{
return 0;
}

unsigned char* normal_buffer;
if (Normals!=0){
normal_buffer=stbi_load(Normals ,&w,&h,0,3);
}else{
normal_buffer=0;
}

unsigned char* height_buffer;
if (Heights!=0){
height_buffer=stbi_load(Heights ,&w,&h,0,1);
}else{
height_buffer=0;
}

unsigned char* roughness_buffer;
if (Roughness!=0){
roughness_buffer=stbi_load(Roughness ,&w,&h,0,1);
}else{
roughness_buffer=0;
}

unsigned char* metallic_buffer;
if (Metallic!=0){
metallic_buffer=stbi_load(Metallic ,&w,&h,0,1);
}else{
metallic_buffer=0;
}

unsigned char* ao_buffer;
if (AO!=0){
ao_buffer=stbi_load(AO ,&w,&h,0,1);
}else{
ao_buffer=0;
}

unsigned char* emissive_buffer;
if (Emissive!=0){
emissive_buffer=stbi_load(Emissive ,&w,&h,0,4);
}else{
emissive_buffer=0;
}

PBRTexture* tex = CreatePBRTexture(width, height, color_buffer, normal_buffer, height_buffer, roughness_buffer, metallic_buffer, ao_buffer, emissive_buffer);

if (color_buffer!=0) delete color_buffer;
if (normal_buffer!=0) delete normal_buffer;
if (height_buffer!=0) delete height_buffer;
if (roughness_buffer!=0) delete roughness_buffer;
if (metallic_buffer!=0) delete metallic_buffer;
if (ao_buffer!=0) delete ao_buffer;
if (emissive_buffer!=0) delete emissive_buffer;

return tex;

}
extern "C" void EntityPBRTexture(Entity* ent, PBRTexture* tex){
EntityTexture (ent, tex->basecolor,0,0);
EntityTexture (ent, tex->normals,0,1);
EntityTexture (ent, tex->PBR,0,2);
EntityTexture (ent, tex->EMI,0,3);
if (ENV!=0){
EntityTexture (ent, ENV,0,4);
}
}

extern "C" Shader* CreatePBR(PBRTexture* tex){
Shader* pbr=CreateShader((char*)"PBR", (char*)pbr_vert_shader, (char*)pbr_frag_shader);

if (tex!=0){
if (tex->basecolor!=0){
ShaderTexture(pbr,tex->basecolor, (char*)"albedoMap",0);
SetInteger(pbr, (char*)"texAL",1);
}else{
SetInteger(pbr, (char*)"texAL",0);
}
if (tex->normals!=0){
ShaderTexture(pbr,tex->normals, (char*)"normalMap",1);
SetInteger(pbr, (char*)"texNM",1);
}else{
SetInteger(pbr, (char*)"texNM",0);
}
if (tex->PBR!=0){
ShaderTexture(pbr,tex->PBR, (char*)"propMap",2);
SetInteger(pbr, (char*)"texPR",1);
}else{
SetInteger(pbr, (char*)"texPR",0);
}
if (tex->EMI!=0){
ShaderTexture(pbr,tex->EMI, (char*)"emissionMap",3);
SetInteger(pbr, (char*)"texEM",1);
}else{
SetInteger(pbr, (char*)"texEM",0);
}
if (ENV!=0){
ShaderTexture(pbr, ENV, (char*)"envMap",4);
SetInteger(pbr, (char*)"setENV",1);
}else{
SetInteger(pbr, (char*)"envMap",4);
SetInteger(pbr, (char*)"setENV",0);
}
}else{
SetInteger(pbr, (char*)"albedoMap",0);
SetInteger(pbr, (char*)"texAL",1);
SetInteger(pbr, (char*)"normalMap",1);
SetInteger(pbr, (char*)"texNM",1);
SetInteger(pbr, (char*)"propMap",2);
SetInteger(pbr, (char*)"texPR",1);
SetInteger(pbr, (char*)"emissionMap",3);
SetInteger(pbr, (char*)"texEM",1);
SetInteger(pbr, (char*)"envMap",4);
}

SetFloat2(pbr, (char*)"texscale", 1,1);
SetFloat2(pbr, (char*)"texoffset", 0,0);
SetFloat(pbr, (char*)"levelscale", 128);
SetFloat(pbr, (char*)"POmulti", 1);
SetFloat(pbr, (char*)"gamma", 1);
return pbr;
}
Title: Re: OpenB3D PBR Shader Demo
Post by: markcwm on April 26, 2023, 12:06:52
Looks good Angros, will this be added to a future update of OpenB3dPlus? Do you have any example usage yet?
Title: Re: OpenB3D PBR Shader Demo
Post by: angros47 on April 26, 2023, 20:57:02
It will, yes. I made some examples in FreeBasic, although they need some cleaning
Title: Re: OpenB3D PBR Shader Demo
Post by: angros47 on August 02, 2023, 00:50:58
Added the files on sourceforge, also created a repository on github: https://github.com/angros47/OpenB3D-Plus (https://github.com/angros47/OpenB3D-Plus)
Title: Re: OpenB3D PBR Shader Demo
Post by: Midimaster on August 02, 2023, 12:32:18
Quote from: Krischan on March 08, 2023, 00:21:27... Anyway, I've completely revised the shaders, cleaned them of unused variables and moved some stuff from the Vertex to the Fragment Shader which had almost no FPS impact but makes the code much cleaner.... Again, the download link: ShaderExample.zip (https://www.christianhart.de/bmax/ShaderExample.zip)

Quote from: angros47 on August 02, 2023, 00:50:58Added the files on sourceforge, also created a repository on github: https://github.com/angros47/OpenB3D-Plus (https://github.com/angros47/OpenB3D-Plus)


Krischan, I'm completely flashed about the possibilities you both created with OpenB3D on a BlitzMax NG. This is so much more realisitc than the old school Blitz-3D or MiniB3D I was used. Thank you for that example! And thanks to all those people like Angros47, which still improve the 3D code an keep it up-to-date.

Now I have 6 weeks of holiday and I plan to study your code and try to understand what happens. The best chance to learn is always to write a TUTORIAL for Newbies. But as I'm zero experienced in this Shaders I will come to my limits...

Would it be possible if you help me, when I have stupid questions?


Questions like this:

You created this shader demo. Inside there are these "Shaders".... also code .... But is this code only usefull for this demo, or are they universal? So to say: This shader can also improve other games without changing anything? Or in other words: Do I have to code a new shader for every project I write?

thank you.

Midimaster

Title: Re: OpenB3D PBR Shader Demo
Post by: Midimaster on August 04, 2023, 08:36:47
FIXED OR DYNAMIC?

Do I understand right, that these shaders are calculating the reacton of lights on an object in real time?

I try to move the object to find out, whether the light situation changes or is fixed on the surface of the object. Means: If the object is more on the left I would expect to see the lights on the "EAST SIDE" of the object. When it comes to the right, I would expect the light now appear on the WEST SIDE of the object.

My impression is, that the lights are "moving".


LOADING OF SHADERS?

In your InitShader function you load two shader parts independent. I was able to reduce your both functions InitShader() and PrepareShader() to only one code line:
Function PrepareShader_II:TShader()
    Return LoadShader("", "shader/pbr.vert", "shader/pbr.frag")
End Function

What is the advantage not to use the given LoadShader() function?



Mandatory Flags?

I could reduce your example code to a minumum of 120 code lines. In the function SetShaderTexture there are a lot of Flags to be set. As I do not know, what they all are good for. I think they are mostly mandatory?

Function SetShaderTexture(shader:TShader, File:String, texscale:Float[])
    'expects upto 4 images for the texures. names:
    '    xxx_AL.TGA   xxx_NM.TGA      xxx_PR.TGA      xxx_EM.TGA
    For Local i:Int=0 To 3
        LoadThisShader shader, i, File
    Next
   
    ' Environment Mapping Texture: Shiny reflective on metals or dull on non-metals
    ShaderTexture(shader, CreateTexture(32 * 6, 32, 1 + 128), "envMap", 4)

    ' Set additional attributes
    SetFloat(shader, "levelscale", 1) ' Scale
    SetFloat(shader, "gamma", 1.0)             ' Gamma
    SetFloat(shader, "POmulti", 1.0)           ' Parallax Occlusion multiplicator

    ' Texture Scale and Offset
    SetFloat2(shader, "texscale", texscale[0], texscale[1])

    ' OpenGL lights
    For Local i:Int = 0  Until  OPENGLIGHTS.length
         SetFloat(shader, "lightradius[" + i + "].Float", OPENGLIGHTS_RANGE[i])
    Next

    ' Light Falloff - see https://www.desmos.com/calculator/nmnaud1hrw
    SetFloat(shader, "A", 1.0)    ' Light: A value
    SetFloat(shader, "B", 10.0)   ' Light: B value

    ' Variable Flags
    SetInteger(shader, "flagPB", 1) ' use PBR Shading
    SetInteger(shader, "flagPM", 1) ' use Parallax Mapping
    SetInteger(shader, "flagEN", 1) ' use Environment Mapping
    SetInteger(shader, "flagEM", 1) ' use Emissive
    SetInteger(shader, "flagTM", 1) ' use Tone Mapping model

End Function


As I now understand the flag and variables, which I can see in the BlitzMax code are not general Shader variables, but defined extra in this specific shader? Is has to do something with the uniform command in the shader script. Here they are defined and that why I can use them on the BlitzMAx side?


Recalculate when change the room?

As I understand, the shader textures are build from a combination of the script and the light situation of the current room. When the object leaves the room, I need to calculate new textures?
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on August 30, 2023, 00:02:21
So many questions :D Sorry I've just seen this - I'm not so often here, more in the Metaverse or Blender ;D

First, my download links have changed but I am somehow not able to edit the old posts anymore. Everything is still there, but use the Link in my signature instead of the links in the posts (sorry for the mess).

Only some quick answers atm:
- the shader demo is mostly an implementation of what you can find at LearnOpenGL (https://learnopengl.com/PBR/Theory) plus some own work
- I really recommend to start there to learn about simple shaders first before you can understand my demo
- also you should read the other pages there to learn more about how shaders are working
 - I think there are different ways to load the shader with OpenB3D
- I've only created my own loading function suitable for the demo (its a crippled derivate of my game code)
- you can either use the max. 8 OpenGL hardware lights or create your own "virtual" lights within the shader
- the shader always calculates PARALLEL all pixels at the same time, this is important to understand!
- so the shader code actually is a calculation of what color/alpha a pixel has at the end of all of these calculations
- and it is calculated/used for ALL pixels at the same time using the same code!
- so the shader affects all surfaces with are "painted" with the shader(s) (ShadeEntity/ShadeSurface)
- the lights then light these surfaces according to their distance/intensity/color
- and the lights make use of the normal, the roughness and metallic reflection, which are additional textures (PBR)
- moving "shader painted" objects in the Blitzmax 3D scene also change their lighting angle, which is what we want
- and moving lights does the same with objects, it's all about the angle between light source and the texel/pixel
- you can reuse the shaders if you pass the same input variables to the shader, otherwise you must adjust it for each project

As PBR with multiple lights can already get quite complex I can only recommend that you first play around with simple 2D shaders at Shadertoy (https://www.shadertoy.com/) which is a great site to learn and to try and error. I've made some simple shaders or altered more complex ones there, I'm also using a 2D fire shader in the demo on a sprite.
https://www.shadertoy.com/user/Krischan (https://www.shadertoy.com/user/Krischan)

You should also "test" your shader with the OpenGL Reference Compiler tool which you can download here and I use it all the time: https://github.com/KhronosGroup/glslang/releases/tag/master-tot (https://github.com/KhronosGroup/glslang/releases/tag/master-tot) You can simply compile it in commandline using for example: glslangValidator.exe pbr.frag

 Shaders must also be tested for Nvidia, Ati and Intel as you might have read in this thread. Working on Nvidia does not mean that it is working on Ati too.

And either it's ok or you'll get error messages with line numbers and some information what went wrong and where to look at. In Shadertoy, you instantly see the error lines in red color.
Title: Re: OpenB3D PBR Shader Demo
Post by: Midimaster on September 06, 2023, 08:41:09
Thank you for the informations. I now play around with the links you gave me. I understand, that shaders are algos, that "auto-draw" visible things and effect on the canvas, instead of creating them with entities or loading them as PNG.

My first target is not creating new shaders, but use given ones in BlitzMax NG+OpenB3D. This week I played around with all those "flat" 2D-examples you paint to a sprite:


Import Openb3d.B3dglgraphics

Graphics3D 1024, 1024, 0, 2

Local gw:Int = GraphicsWidth()
Local gh:Int = GraphicsHeight()
...
Local Camera:TCamera = CreateCamera()
Local test:TSprite = CreateSprite(camera)
PositionEntity test, 0, 0, 1

Local shader:TShader = LoadShader("", "shaders/basic.vert", "shaders/colors2.frag")
SetFloat2(shader, "resolution", gw*1.0, gh*1.0)
ShadeEntity(test, shader)

Repeat
RenderWorld
Flip
until AppTerminate()
End

You mostly use the same BlitzMax code for startup all the examples. I tried now to vary the code, but I saw some unexpected effects. Here I need your advise.


When I move the Sprite away (z+) from the camera, the effect on its surface does not become smaller( like zoom), but it shows a smaller content of the shader. The same, when I move the Sprite from left to right. It looks like the shader-pixels stay at a given position. So to say, the shader is connected to the z=0-level of the camera, painted only where the Sprite is visible to the camera.

To use effects like Fire, it would be necessary to scale it and position it free in the 3D space. Is this possible with shaders too, or is this not the right way to use shaders?

or....
How ca we combine shaders that simulate seaside waves with a given BlitzMax island terrain? and a plane?
 
Title: Re: OpenB3D PBR Shader Demo
Post by: William on September 06, 2023, 12:43:29
thanks @Krischan 
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on September 11, 2023, 18:46:24
I've created a small demo to visualize it how to apply a 2D shader on a 3D surface - here a cube and a sphere at the same time. You can also change the "lava" to "water" in line 10 to see a different shader. You can move around with WASD and the Mouse to see it from different angles/positions.

It's difficult to explain - you must understand how Local Space, World Space and View Space are different and how the calculation of positions/angles in the shader works. See Coordinate Systems (https://learnopengl.com/Getting-started/Coordinate-Systems) in LearnOpenGL

Vertex Shader:
#version 130

uniform vec2 scale;

out vec2 Vertex_UV;
out vec4 Vertex_Color;

void main()
{
Vertex_UV = gl_MultiTexCoord0.xy*scale;
Vertex_Color = gl_Color;
gl_Position = ftransform();
}

Fragment Shader:
#version 130

precision mediump float;

uniform float time;
uniform sampler2D texture1;
uniform sampler2D texture2;

in vec2 Vertex_UV;
in vec4 Vertex_Color;
out vec4 fragColor;

void main( void ) {

vec2 uv = Vertex_UV;
vec2 position = - 1.0 + 2.0 * uv;

vec4 noise = texture2D( texture2, uv/4.0 );
vec2 T1 = uv + vec2( 1.5, - 1.5 ) * time * 0.02;
vec2 T2 = -uv;// + vec2( - 0.5, 2.0 ) * time * 0.01;

T1.x += noise.x * 2.0;
T1.y += noise.y * 2.0;
T2.x -= noise.y * 0.2;
T2.y += noise.z * 0.2;

float p = texture2D( texture2, T1 /2.0 ).a;

vec4 color = texture2D( texture2, T2  );
vec4 temp = color * ( vec4( p, p, p, p ) * 2.0 ) + ( color * color - 0.1 );

if( temp.r > 1.0 ) { temp.bg += clamp( temp.r - 2.0, 0.0, 100.0 ); }
if( temp.g > 1.0 ) { temp.rb += temp.g - 1.0; }
if( temp.b > 1.0 ) { temp.rg += temp.b - 1.0; }

fragColor = vec4(temp.rgb*Vertex_Color.rgb,0.5+Vertex_Color.a);

}

Demo Source:
SuperStrict

Framework brl.basic
Import openb3d.B3dglgraphics
Import brl.timerdefault

Const SCREEN_WIDTH:Int = 1920
Const SCREEN_HEIGHT:Int = 1080
Const FPS:Int = 60
Const shader:String = "lava"

Graphics3D SCREEN_WIDTH, SCREEN_HEIGHT, 32, 2

' movement
Global xs:Float, ys:Float
Global ya:Float, yb:Float
Global px:Float, py:Float
Global pitch:Float
Global yaw:Float

' timing and FPS
Global timer:TTimer = CreateTimer(FPS)
Global timed:Float

' camera pivot
Global player:TPivot = CreatePivot()
PositionEntity player, 0, 4, 0

' camera
Global camera:TCamera = CreateCamera(player)
CameraClsColor camera, 0, 0, 0
pitch = 0.0

' lava object
Local lava:TEntity = CreateCube()
EntityFX lava, 1 + 2 + 32
ScaleEntity lava, 32, 0, 32
EntityBlend lava, 3

' spinning cube
Local test:TEntity = CreateSphere(32)
PositionEntity test, 0, 4, 4
EntityFX test, 1
EntityBlend test, 1

' lava shader
Local lavashader:TShader = LoadShader("", shader + ".vert", shader + ".frag")
Local tex0:TTexture = LoadTexture("cloud.png")
Local tex1:TTexture = LoadTexture("lava.png")
ShaderTexture(lavashader, tex0, "texture1", 0)
ShaderTexture(lavashader, tex1, "texture2", 1)
SetFloat2(lavashader, "scale", 4, 4)
UseFloat(lavashader, "time", timed)
ShadeEntity lava, lavashader
ShadeEntity test, lavashader

While Not KeyHit(KEY_ESCAPE)

' shader timer
timed = Float((TimerTicks(timer) * 1.0 / FPS)) * 4.0

' movement
MoveMouse(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2)
Movement()

TurnEntity test, 0.1, 0.2, 0.3

RenderWorld

Flip

Wend

End

' camera movement
Function Movement()

Local mx:Int = MouseX()
Local my:Int = MouseY()

' mouse moved?
If mx Or my Then

' get mouse vector
Local mxs:Float = (mx - (SCREEN_WIDTH / 2)) / 20.0
Local mys:Float = (my - (SCREEN_HEIGHT / 2)) / 20.0

' add to temp var
xs:+mxs
ys:+mys

' add mouse vector to rotation matrix
ya:-xs
px:+ys
yb:+(ya - yb)
py:+(px - py)

' rotate camera and pivot
RotateEntity camera, py + pitch, 0, 0
RotateEntity player, 0, yb + yaw, 0

EndIf

' decrease mouse vector = smoother
xs:*0.7
ys:*0.7

' player movement
MoveEntity player, (KeyDown(KEY_D) - KeyDown(KEY_A)) / 4.0, 0, (KeyDown(KEY_W) - KeyDown(KEY_S)) / 4.0

End Function
Title: Re: OpenB3D PBR Shader Demo
Post by: Mongoose on September 22, 2023, 21:43:18
All of this looks fantastic! Though the download link for the initial demo seems to be defunct as it leads to a 404 - Not Found page. 
Title: Re: OpenB3D PBR Shader Demo
Post by: Krischan on October 02, 2023, 00:29:21
Quote from: Mongoose on September 22, 2023, 21:43:18All of this looks fantastic! Though the download link for the initial demo seems to be defunct as it leads to a 404 - Not Found page.
My download links have changed but I am somehow not able to edit the old posts anymore. Everything is still there, but use the Link in my signature instead of the links in the posts (sorry for the mess).