OpenB3D PBR Shader Demo

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

Previous topic - Next topic

Krischan

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

ShaderExample.jpg ShaderExample_minimum.jpg ShaderExample_POM.jpg


Kind regards
Krischan

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

markcwm

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.
 


Krischan

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

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

angros47

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

angros47

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

angros47

#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;
}

markcwm

Looks good Angros, will this be added to a future update of OpenB3dPlus? Do you have any example usage yet?

angros47

It will, yes. I made some examples in FreeBasic, although they need some cleaning

angros47

Added the files on sourceforge, also created a repository on github: https://github.com/angros47/OpenB3D-Plus

Midimaster

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

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


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

...back from North Pole.

Midimaster

#55
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?
...back from North Pole.

Krischan

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

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

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

Midimaster

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?
 
...back from North Pole.

William

I'm just a mentally ill person stuck in person. I've had quite an online history and of media such as role plays with people that became movies (for real) among other things and content. I wanted to be an online famous person at one time. Unfortunately it's ill and I'm anonymous.

Krischan

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

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