Background
I use this app to test future PBR shaders since I'm planning on getting into game development once I get a new laptop (I have no other resource). Examples of work I do with my the app are manual texture baking, reverse engineering in game shaders.
1: Include files
For example this is my SH projected GGX shader.
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
uniform vec2 resolution;
uniform float time;
uniform sampler2D bansheh2nm;
uniform sampler2D uv_16_16;///min:n;mag:n;
uniform sampler2D uv_32_32;///min:n;mag:n;s:r;t:r;
uniform sampler2D uv_64;///min:n;mag:n;
#define invert(x) (1.0 - saturate(x) )
#define mux(x, y) r0.a >= 0.5 ? x : y
#define saturate(x) clamp(x, 0.0, 1.0)
#define expand(x) ( clamp(x, 0.0, 1.0) * 2.0 - 1.0)
#define sqr(x) (x*x)
#define tex2D(x, uv) texture2D(x, uv)
#define colorRGB(r, g, b) (vec3(r, g, b) /255.0)
#define pi 3.1415926535897932384626433832795028841971f
#define epsilon 0.000000001f
#define SRCCOLOR gl_FragColor.rgb
#define SRCALPHA gl_FragColor.a
#define SRCOUT gl_FragColor
float V_Schlick_GGX(in float _gloss, in float n_l, in float n_v){
float r4 = (1.0 -_gloss);
float k, g1, g2;
k = pow(r4 + 1.0, 2.0) / 8.0;
g1 = (n_v * (1.0 - k) + k);
g2 = (n_l * (1.0 - k) + k);
return 0.5 / ( 2. * (g1 * g2) );
}
float D_Trowbridge_Reitz(
in float _gloss,
in float n_h
){
//float a2 = pow(_gloss, 4.0);
float r4 = (1.0 - _gloss);
// r4 = pow(r4, 1.0);
// r4 = at * ab;
float n_h2 = pow(n_h, 2.0);
float denom = n_h2 * ((r4) -1.0) + 1.0;
denom = max(pi * (denom * denom), epsilon);
return r4 / denom;
}
vec2 paraboloid_project(vec3 v) {
vec3 n = normalize(v + vec3(0.0, 0.0, 1.0));
return n.xy / n.z;
}
vec3 paraboloid_unproject(vec2 xy) {
float z = (1.0 - dot(xy, xy)) / 2.0;
return normalize(vec3(xy, z));
}
vec4 toRGBE(vec3 c) {
vec3 c_abs = abs(c);
float x = max(c_abs.r, max(c_abs.g, c_abs.b));
float y = ceil(log2(x));
return x <= 0.0 ? vec4(0) : vec4(c / exp2(y), (y + 128.0) / 255.0);
}
float sh_project_band0 = 1.0/2.0 * sqrt(1.0/pi);
void sh_project_band1(vec3 n, out float Y1[3]) {
Y1[0] = sqrt(3.0/4.0/pi) * n.y;
Y1[1] = sqrt(3.0/4.0/pi) * n.z;
Y1[2] = sqrt(3.0/4.0/pi) * n.x;
}
void sh_project_band2(vec3 n, out float Y2[5]) {
vec3 n2 = n * n;
Y2[0] = 1.0/2.0 * sqrt(15.0/pi) * n.x * n.y;
Y2[1] = 1.0/2.0 * sqrt(15.0/pi) * n.y * n.z;
Y2[2] = 1.0/4.0 * sqrt( 5.0/pi) * (2.0*n2.z - n2.x - n2.y);
Y2[3] = 1.0/2.0 * sqrt(15.0/pi) * n.z * n.x;
Y2[4] = 1.0/4.0 * sqrt(15.0/pi) * (n2.x - n2.y);
}
void ComputeSHBasis(vec3 normal, out float sh[9])
{
// Band 0
sh[0] = 0.282095f;
// Band 1
sh[1] = 0.488603f * normal.y;
sh[2] = 0.488603f * normal.z;
sh[3] = 0.488603f * normal.x;
// Band 2
sh[4] = 1.092548f * normal.x * normal.y;
sh[5] = 1.092548f * normal.y * normal.z;
sh[6] = 0.315392f * (2. * sqr(normal.z) - sqr(normal.x)- sqr(normal.y) );
sh[7] = 1.092548f * normal.x * normal.z;
sh[8] = 0.546274f * (normal.x * normal.x - normal.y * normal.y);
}
void shEvaluate(vec3 n, out vec3 sh_coeff[9] ){
// 9 SH coefficients (usually passed as uniforms)
vec3 sh[9];
// Constants
const float PI = 3.14159265359;
const float C1 = 0.429043;
const float C2 = 0.511664;
const float C3 = 0.743125;
const float C4 = 0.886227;
const float C5 = 0.247708;
// Band 0
vec3 res = C4 * sh[0];
// Band 1
res += -C2 * sh[1] * n.y;
res += C2 * sh[2] * n.y; // Standard orderings can vary
res += -C2 * sh[2] * n.x;
// Band 2
res += C1 * sh[4] * n.x * n.y;
res += C1 * sh[5] * n.y * n.z;
res += C5 * sh[6] * (3.0 * n.z * n.z - 1.0);
res += C1 * sh[7] * n.y * n.z;
res += C3 * sh[8] * (n.x * n.x - n.y * n.y);
}
vec3 getParabolicViewDir(vec2 p) {
vec2 xy = 2.0 * p * sqrt(1.0 - (p.x * p.x) - (p.y * p.y) );
float z = 1.0 - 2.0 * dot(p, p);
return vec3(xy, z);
}
vec3 xy_xyz(vec2 xy){
float z = sqrt(1.0 - (xy.x * xy.x) * (xy.y * xy.y) );
return vec3(xy, z);
}
vec2 parabaolic_parameterize(vec3 v){
vec2 res;
res.x = v.x / (1.0 + v.z);
res.y = v.y / (1.0 + v.z);
return res;
}
float V_SmithGGXCorrelated (float gloss, float NdotL , float NdotV )
{
// Original formulation of G_SmithGGX Correlated
// lambda_v = ( -1 + sqrt ( alphaG2 * (1 - NdotL2 ) / NdotL2 + 1)) * 0.5 f;
// lambda_l = ( -1 + sqrt ( alphaG2 * (1 - NdotV2 ) / NdotV2 + 1)) * 0.5 f;
// G_SmithGGXCorrelated = 1 / (1 + lambda_v + lambda_l );
// V_SmithGGXCorrelated = G_SmithGGXCorrelated / (4.0 f * NdotL * NdotV );
// This is the optimize version
float rough = 1. - gloss;
float alphaG2 = rough ;
float Lambda_GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - alphaG2) + alphaG2);
float Lambda_GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - alphaG2) + alphaG2);
return 0.5 / max((Lambda_GGXV + Lambda_GGXL), 0.00001);
}
float D_Beckmann(float _gloss, float N_H){
float rough = 1.0 - min(_gloss, 0.857375);
float NH2 = N_H * N_H;
float nom = exp( (NH2 - 1.0 )/ (rough * NH2) );
float denom = (rough * NH2 * NH2);
return nom / max(denom, 0.001f);
}
float G_CT(float V_H, float N_H, float N_Xmin){
float nom = 2.0 * N_H * N_Xmin;
return nom / max(V_H, 0.001f);
}
vec3 quantize16(vec3 color) {
return floor(color * 65535.0 + 0.5) / 65535.0;
}
void main(void) {
vec2 uv = gl_FragCoord.xy / resolution.xy;
uv += - 0.5;
uv = (resolution.y > resolution.x) ? uv*vec2(1.0, resolution.y/resolution.x) : uv*vec2(resolution.x/resolution.y, 1.0);
uv += 0.5;
uv = mod(uv, vec2(1) );
//
//
// uv.y = 1.-uv.y;
// uv = tex2D(uv_64, uv).xy;
vec3 L = xy_xyz(uv * vec2(2.0, -2.0) - vec2(1.0, -1.0)) ;
L = colorRGB(0, 0, 255);
// L.z = cos(L.z);
// L.x = sqrt(1. - (L.x * L.x) );
// L =
vec3 Y1_3[3], Y4_8[5];
/*if ( L.z < 0.0){
discard;
}
*/
vec3 N = vec3(0,0, 1) ;
// N.xy = uv * 2. -1.;
// N.z = sqrt(1. - (N.x * N.x) - (N.y * N.y) );
//N.y = 0.;
//N.xy = vec2(0.);
//
// N = tex2D(bansheh2nm, uv).xyz * 2.0 - 1.0;
// float viewing_angle = cos(time) / 2.0 + 0.5;
// viewing_angle = uv.xy
vec3 V = vec3(sqrt(1. - uv.x * uv.x), 0 , cos(uv.x) );
// V = V - dot(N, V) * N;
mat3 local_frame;
local_frame[0] = V - dot(N, V) * N;
local_frame[2] = N;
local_frame[1] = cross(local_frame[0], N);
V *= local_frame;
// L = V;
//L = L - dot(N, L) * N;
vec3 H = normalize(L + V);
// H.z = 1.0 - H.z;
V = min(V, vec3(1.) );
vec4 c0236, d0236, cd7878;
// for(float i = 0.; i < 1.0; i += (1. / 64.)){
// L = vec3(i);
L = min(L, vec3(1.0) );
float N_L, N_V, N_H;
float V_H = dot(V, H);
float L_H = dot(L, H);
N_H = dot(N, H);
N_L = dot(N, L);
// N_V = uv.x;
N_V = dot(N, V);
float gloss = pow( uv.y, 1.);
// gloss = pow(0.93, 1.);
float D = D_Trowbridge_Reitz(gloss, N_H);
float Vis = V_SmithGGXCorrelated(gloss, N_L, N_V);
float Specular = D * Vis ;
// Specular /= 2.;
// Specular /= 4.;
//Specular = L.z;
// Specular *= pow(1.0 - N_V, 5.0);
// Specular = D * Vis * pow(1.0 - N_V, 5.0);
// Specular = D;
// SRCCOLOR = toRGBE( vec3(Specular)).xyz;
float Y[9];
ComputeSHBasis(N, Y);
SRCOUT = vec4(Specular * 2.0);
float df = 1. - pow( max(dot(L, H), 0.001) , 5.0);
c0236 = Specular * L.z * vec4( Y[0], Y[2], Y[3], Y[6]) ;
d0236 = Specular * L.z * df * vec4( Y[0], Y[2], Y[3], Y[6]);
cd7878.rg = Specular * vec2( Y[7], Y[8] );
cd7878.ba = Specular * df * vec2( Y[7], Y[8]);
// }
// SRCOUT =
SRCOUT = c0236 * 0.5 + 0.5;
SRCOUT = c0236;
SRCOUT = vec4(Specular / 2.);
// SRCCOLOR = (1.0 - SRCOUT.a) + SRCOUT.rgb * SRCOUT.a;
// SRCCOLOR = vec3(Y[3] );
// SRCCOLOR = L;
// SRCOUT = d0236 * 0.5 + 0.5;
// SRCCOLOR = vec3(Y[7], Y[8], 0.);
// SRCOUT = vec4(d0236) ;
// SRCOUT = c0236 * 1.;
// SRCCOLOR = quantize16(SRCCOLOR);
// SRCCOLOR = vec3(Specular * N_L ) / 1. + 0.;
// SRCCOLOR = vec3(1.0 - N_V);
// SRCCOLOR = vec3(c0236[3] ) ;
// SRCCOLOR = vec3(N_V);
// SRCCOLOR = V;
// SRCCOLOR = vec3(L.zzz);
// SRCCOLOR = vec3(N_V);
// SRCCOLOR = vec3(uv, 0.0);
}
It would streamline the workflow if the functions are split across multiple files.
2. Expanded image format support and exporting
PNGs are enough for most applications yes but the thing is, it'snot always enough. TIFF files support float, which would help for generating and exporting shaders as lookup tables. The shader I showed earlier would also benefit from it as encoding it to unorm and screenshotting results in compression artifacts.
3. Extended sampler features
A. Per shader uniform reference.
While shared uniforms are great, it's tedious to have to declare a new uniform if you're trying to test out shaders and compare newer and previous versions.
B. Support Sampler2D Arrays
If the previous one can't be implemented then 2D arrays will help since each element will contain multiple versions.
Related to the previous one.
Background
I use this app to test future PBR shaders since I'm planning on getting into game development once I get a new laptop (I have no other resource). Examples of work I do with my the app are manual texture baking, reverse engineering in game shaders.
1: Include files
For example this is my SH projected GGX shader.
It would streamline the workflow if the functions are split across multiple files.
2. Expanded image format support and exporting
PNGs are enough for most applications yes but the thing is, it'snot always enough. TIFF files support float, which would help for generating and exporting shaders as lookup tables. The shader I showed earlier would also benefit from it as encoding it to unorm and screenshotting results in compression artifacts.
3. Extended sampler features
A. Per shader uniform reference.
While shared uniforms are great, it's tedious to have to declare a new uniform if you're trying to test out shaders and compare newer and previous versions.
B. Support Sampler2D Arrays
If the previous one can't be implemented then 2D arrays will help since each element will contain multiple versions.
Related to the previous one.