UNPKG

pex-renderer

Version:

Physically Based Renderer for Pex

532 lines (440 loc) 12.9 kB
module.exports = /* glsl */ ` #if NUM_AREA_LIGHTS > 0 struct AreaLight { vec3 position; vec2 size; vec4 color; float intensity; vec4 rotation; }; uniform AreaLight uAreaLights[NUM_AREA_LIGHTS]; uniform sampler2D ltc_mat; uniform sampler2D ltc_mag; uniform mat4 view; const vec2 resolution = vec2(1280.0, 720.0); const int sampleCount = 4; const int NUM_SAMPLES_3020430251 = 8; const float LUT_SIZE_3020430251 = 64.0; const float LUT_SCALE_3020430251 = (LUT_SIZE_3020430251 - 1.0)/LUT_SIZE_3020430251; const float LUT_BIAS_3020430251 = 0.5/LUT_SIZE_3020430251; const float pi_0 = 3.14159265; // Tracing and intersection /////////////////////////// /// /// struct Ray { vec3 origin; vec3 dir; }; struct Rect { vec3 origin; vec4 plane; float sizex; float sizey; }; bool RayPlaneIntersect(Ray ray, vec4 plane, out float t) { t = -dot(plane, vec4(ray.origin, 1.0))/dot(plane.xyz, ray.dir); return t > 0.0; } bool RayRectIntersect(Ray ray, Rect rect, out float t) { bool intersect = RayPlaneIntersect(ray, rect.plane, t); if (intersect) { vec3 pos = ray.origin + ray.dir*t; vec3 lpos = pos - rect.origin; if (abs(lpos.x) > rect.sizex || abs(lpos.y) > rect.sizey) intersect = false; } return intersect; } // Adapted from: // https://www.shadertoy.com/view/4djSRW float hash(float x, float y) { vec2 p = vec2(x, y); p = fract(p * vec2(443.8975, 397.2973)); p += dot(p.xy, p.yx + 19.19); return fract(p.x + p.y); } //TODO: which coordinate space? Ray GenerateCameraRay(float u1, float u2) { Ray ray; // Random jitter within pixel for AA, huh? what jitter vec2 xy = 2.0*(gl_FragCoord.xy)/resolution - vec2(1.0); ray.dir = normalize(vec3(xy, 2.0)); float focalDistance = 2.0; float ft = focalDistance/ray.dir.z; vec3 pFocus = ray.dir*ft; ray.origin = vec3(0); ray.dir = normalize(pFocus - ray.origin); // Apply camera transform ray.origin = (view*vec4(ray.origin, 1)).xyz; ray.dir = (view*vec4(ray.dir, 0)).xyz; return ray; } vec3 mul(mat3 m, vec3 v) { return m * v; } mat3 mul(mat3 m1, mat3 m2) { return m1 * m2; } int modi(int x, int y) { return int(mod(float(x), float(y))); } mat3 transpose(mat3 v) { mat3 tmp; tmp[0] = vec3(v[0].x, v[1].x, v[2].x); tmp[1] = vec3(v[0].y, v[1].y, v[2].y); tmp[2] = vec3(v[0].z, v[1].z, v[2].z); return tmp; } struct SphQuad { vec3 o, x, y, z; float z0, z0sq; float x0, y0, y0sq; float x1, y1, y1sq; float b0, b1, b0sq, k; float S; }; SphQuad SphQuadInit(vec3 s, vec3 ex, vec3 ey, vec3 o) { SphQuad squad; squad.o = o; float exl = length(ex); float eyl = length(ey); // compute local reference system ’R’ squad.x = ex / exl; squad.y = ey / eyl; squad.z = cross(squad.x, squad.y); // compute rectangle coords in local reference system vec3 d = s - o; squad.z0 = dot(d, squad.z); // flip ’z’ to make it point against ’Q’ if (squad.z0 > 0.0) { squad.z *= -1.0; squad.z0 *= -1.0; } squad.z0sq = squad.z0 * squad.z0; squad.x0 = dot(d, squad.x); squad.y0 = dot(d, squad.y); squad.x1 = squad.x0 + exl; squad.y1 = squad.y0 + eyl; squad.y0sq = squad.y0 * squad.y0; squad.y1sq = squad.y1 * squad.y1; // create vectors to four vertices vec3 v00 = vec3(squad.x0, squad.y0, squad.z0); vec3 v01 = vec3(squad.x0, squad.y1, squad.z0); vec3 v10 = vec3(squad.x1, squad.y0, squad.z0); vec3 v11 = vec3(squad.x1, squad.y1, squad.z0); // compute normals to edges vec3 n0 = normalize(cross(v00, v10)); vec3 n1 = normalize(cross(v10, v11)); vec3 n2 = normalize(cross(v11, v01)); vec3 n3 = normalize(cross(v01, v00)); // compute internal angles (gamma_i) float g0 = acos(-dot(n0, n1)); float g1 = acos(-dot(n1, n2)); float g2 = acos(-dot(n2, n3)); float g3 = acos(-dot(n3, n0)); // compute predefined constants squad.b0 = n0.z; squad.b1 = n2.z; squad.b0sq = squad.b0 * squad.b0; squad.k = 2.0*pi_0 - g2 - g3; // compute solid angle from internal angles squad.S = g0 + g1 - squad.k; return squad; } vec3 SphQuadSample(SphQuad squad, float u, float v) { // 1. compute 'cu' float au = u * squad.S + squad.k; float fu = (cos(au) * squad.b0 - squad.b1) / sin(au); float cu = 1.0 / sqrt(fu*fu + squad.b0sq) * (fu > 0.0 ? 1.0 : -1.0); cu = clamp(cu, -1.0, 1.0); // avoid NaNs // 2. compute 'xu' float xu = -(cu * squad.z0) / sqrt(1.0 - cu * cu); xu = clamp(xu, squad.x0, squad.x1); // avoid Infs // 3. compute 'yv' float d = sqrt(xu * xu + squad.z0sq); float h0 = squad.y0 / sqrt(d*d + squad.y0sq); float h1 = squad.y1 / sqrt(d*d + squad.y1sq); float hv = h0 + v * (h1 - h0), hv2 = hv * hv; float yv = (hv2 < 1.0 - 1e-6) ? (hv * d) / sqrt(1.0 - hv2) : squad.y1; // 4. transform (xu, yv, z0) to world coords return squad.o + xu*squad.x + yv*squad.y + squad.z0*squad.z; } // Sample generation //////////////////// float Halton(int index, float base) { float result = 0.0; float f = 1.0/base; float i = float(index); for (int x = 0; x < 8; x++) { if (i <= 0.0) break; result += f*mod(i, base); i = floor(i/base); f = f/base; } return result; } void Halton2D(out vec2 s[NUM_SAMPLES_3020430251], int offset) { for (int i = 0; i < NUM_SAMPLES_3020430251; i++) { s[i].x = Halton(i + offset, 2.0); s[i].y = Halton(i + offset, 3.0); } } // Linearly Transformed Cosines /////////////////////////////// float IntegrateEdge(vec3 v1, vec3 v2) { float cosTheta = dot(v1, v2); cosTheta = clamp(cosTheta, -0.9999, 0.9999); float theta = acos(cosTheta); float res = cross(v1, v2).z * theta / sin(theta); return res; } void ClipQuadToHorizon(inout vec3 L[5], out int n) { // detect clipping config int config = 0; if (L[0].z > 0.0) config += 1; if (L[1].z > 0.0) config += 2; if (L[2].z > 0.0) config += 4; if (L[3].z > 0.0) config += 8; // clip n = 0; if (config == 0) { // clip all } else if (config == 1) // V1 clip V2 V3 V4 { n = 3; L[1] = -L[1].z * L[0] + L[0].z * L[1]; L[2] = -L[3].z * L[0] + L[0].z * L[3]; } else if (config == 2) // V2 clip V1 V3 V4 { n = 3; L[0] = -L[0].z * L[1] + L[1].z * L[0]; L[2] = -L[2].z * L[1] + L[1].z * L[2]; } else if (config == 3) // V1 V2 clip V3 V4 { n = 4; L[2] = -L[2].z * L[1] + L[1].z * L[2]; L[3] = -L[3].z * L[0] + L[0].z * L[3]; } else if (config == 4) // V3 clip V1 V2 V4 { n = 3; L[0] = -L[3].z * L[2] + L[2].z * L[3]; L[1] = -L[1].z * L[2] + L[2].z * L[1]; } else if (config == 5) // V1 V3 clip V2 V4) impossible { n = 0; } else if (config == 6) // V2 V3 clip V1 V4 { n = 4; L[0] = -L[0].z * L[1] + L[1].z * L[0]; L[3] = -L[3].z * L[2] + L[2].z * L[3]; } else if (config == 7) // V1 V2 V3 clip V4 { n = 5; L[4] = -L[3].z * L[0] + L[0].z * L[3]; L[3] = -L[3].z * L[2] + L[2].z * L[3]; } else if (config == 8) // V4 clip V1 V2 V3 { n = 3; L[0] = -L[0].z * L[3] + L[3].z * L[0]; L[1] = -L[2].z * L[3] + L[3].z * L[2]; L[2] = L[3]; } else if (config == 9) // V1 V4 clip V2 V3 { n = 4; L[1] = -L[1].z * L[0] + L[0].z * L[1]; L[2] = -L[2].z * L[3] + L[3].z * L[2]; } else if (config == 10) // V2 V4 clip V1 V3) impossible { n = 0; } else if (config == 11) // V1 V2 V4 clip V3 { n = 5; L[4] = L[3]; L[3] = -L[2].z * L[3] + L[3].z * L[2]; L[2] = -L[2].z * L[1] + L[1].z * L[2]; } else if (config == 12) // V3 V4 clip V1 V2 { n = 4; L[1] = -L[1].z * L[2] + L[2].z * L[1]; L[0] = -L[0].z * L[3] + L[3].z * L[0]; } else if (config == 13) // V1 V3 V4 clip V2 { n = 5; L[4] = L[3]; L[3] = L[2]; L[2] = -L[1].z * L[2] + L[2].z * L[1]; L[1] = -L[1].z * L[0] + L[0].z * L[1]; } else if (config == 14) // V2 V3 V4 clip V1 { n = 5; L[4] = -L[0].z * L[3] + L[3].z * L[0]; L[0] = -L[0].z * L[1] + L[1].z * L[0]; } else if (config == 15) // V1 V2 V3 V4 { n = 4; } if (n == 3) L[3] = L[0]; if (n == 4) L[4] = L[0]; } vec3 LTC_Evaluate_3020430251( vec3 N, vec3 V, vec3 P, mat3 Minv, vec3 points[4], bool twoSided) { // construct orthonormal basis around N vec3 T1, T2; T1 = normalize(V - N*dot(V, N)); T2 = cross(N, T1); // rotate area light in (T1, T2, R) basis Minv = Minv * transpose(mat3(T1, T2, N)); // polygon (allocate 5 vertices for clipping) vec3 L[5]; L[0] = Minv * (points[0] - P); L[1] = Minv * (points[1] - P); L[2] = Minv * (points[2] - P); L[3] = Minv * (points[3] - P); int n; ClipQuadToHorizon(L, n); if (n == 0) return vec3(0, 0, 0); // project onto sphere L[0] = normalize(L[0]); L[1] = normalize(L[1]); L[2] = normalize(L[2]); L[3] = normalize(L[3]); L[4] = normalize(L[4]); // integrate float sum = 0.0; sum += IntegrateEdge(L[0], L[1]); sum += IntegrateEdge(L[1], L[2]); sum += IntegrateEdge(L[2], L[3]); if (n >= 4) sum += IntegrateEdge(L[3], L[4]); if (n == 5) sum += IntegrateEdge(L[4], L[0]); sum = twoSided ? abs(sum) : max(0.0, -sum); vec3 Lo_i = vec3(sum, sum, sum); return Lo_i; } // Misc. helpers //////////////// vec3 PowVec3(vec3 v, float p) { return vec3(pow(v.x, p), pow(v.y, p), pow(v.z, p)); } vec3 check(bool test) { if (test) return vec3(0,1,0); else return vec3(1,0.2,0); } vec3 multQuat(vec3 a, vec4 q){ float x = a.x; float y = a.y; float z = a.z; float qx = q.x; float qy = q.y; float qz = q.z; float qw = q.w; float ix = qw * x + qy * z - qz * y; float iy = qw * y + qz * x - qx * z; float iz = qw * z + qx * y - qy * x; float iw = -qx * x - qy * y - qz * z; a.x = ix * qw + iw * -qx + iy * -qz - iz * -qy; a.y = iy * qw + iw * -qy + iz * -qx - ix * -qz; a.z = iz * qw + iw * -qz + ix * -qy - iy * -qx; return a; } vec3 evalAreaLight(AreaLight light, vec3 posWorld, vec3 normalWorld, vec3 diffuseColor, vec3 specularColor, float roughness) { vec2 seq[NUM_SAMPLES_3020430251]; Halton2D(seq, sampleCount); vec3 col = vec3(0); // Scene info vec3 lcol = toLinear(light.color.rgb) * light.intensity; vec3 dcol = diffuseColor; vec3 scol = specularColor; { Ray ray; ray.origin = uCameraPosition; ray.dir = normalize(posWorld - uCameraPosition); { vec3 pos = posWorld; vec3 N = normalWorld; vec3 V = -ray.dir; //FIXME: why this has to be -1? vec3 ex = multQuat(vec3(-1, 0, 0), light.rotation)*light.size.x; vec3 ey = multQuat(vec3(0, 1, 0), light.rotation)*light.size.y; vec3 p1 = light.position - ex + ey; vec3 p2 = light.position + ex + ey; vec3 p3 = light.position + ex - ey; vec3 p4 = light.position - ex - ey; vec3 points[4]; points[0] = p1; points[1] = p2; points[2] = p3; points[3] = p4; float theta = acos(dot(N, V)); vec2 uv = vec2(roughness, theta/(0.5*pi_0)); uv = uv*LUT_SCALE_3020430251 + LUT_BIAS_3020430251; vec4 t = texture2D(ltc_mat, uv); mat3 Minv = mat3( vec3( 1, 0, t.y), vec3( 0, t.z, 0), vec3(t.w, 0, t.x) ); vec3 spec = lcol*scol*LTC_Evaluate_3020430251(N, V, pos, Minv, points, false); spec *= texture2D(ltc_mag, uv).w; vec3 diff = lcol*dcol*LTC_Evaluate_3020430251(N, V, pos, mat3(1), points, false); col = spec + diff; col /= 2.0*pi_0; } //TODO: how to find out we had hit the screen? //float distToRect; //if (RayRectIntersect(ray, rect, distToRect)) // if ((distToRect < distToFloor) || !hitFloor) // col = lcol; } return col; } void EvaluateAreaLight(inout PBRData data, AreaLight light, float ao) { data.indirectSpecular += ao * evalAreaLight(light, data.positionWorld, data.normalWorld, data.baseColor, data.f0, data.roughness); } #endif `