OpenB3D PBR Shader Demo

Started by Krischan, January 02, 2020, 00:03:18

Previous topic - Next topic

iWasAdam

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 :)

Krischan

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);

}
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

angros47

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?

Krischan

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
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

angros47

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?

Krischan

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).
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

Krischan

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.
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

angros47


markcwm

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

Krischan

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 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.
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

markcwm

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 patches sound like hard math. Here is Paul Bourke's Bezier Surface code.

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.

Krischan

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
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

angros47

A little demo of the new shader would help, I am struggling to pass it parmeters

Krischan

#28
Finally I found some time to create a cool PBR shader demo using Blitzmax NG and OpenB3D. Have fun!

Download (10MB): 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);
   
}

Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

markcwm

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.