molstar
Version:
A comprehensive macromolecular library.
376 lines (308 loc) • 11.4 kB
JavaScript
"use strict";
/**
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.trace_frag = void 0;
exports.trace_frag = `
precision highp int;
precision highp float;
precision highp sampler2D;
uniform sampler2D tColor;
uniform sampler2D tNormal;
uniform sampler2D tShaded;
uniform sampler2D tThickness;
uniform sampler2D tAccumulate;
uniform sampler2D tDepth;
uniform vec2 uTexSize;
uniform vec4 uBounds;
uniform float uNear;
uniform float uFar;
uniform float uFogNear;
uniform float uFogFar;
uniform vec3 uFogColor;
uniform vec3 uLightDirection[dLightCount];
uniform vec3 uLightColor[dLightCount];
uniform vec3 uAmbientColor;
uniform vec3 uLightStrength;
uniform int uFrameNo;
uniform float uRayDistance;
uniform float uMinThickness;
uniform float uThicknessFactor;
uniform float uThickness;
uniform float uShadowSoftness;
uniform float uShadowThickness;
uniform mat4 uProjection;
uniform mat4 uInvProjection;
// parts adapted from
// - https://blog.demofox.org/2020/05/25/casual-shadertoy-path-tracing-1-basic-camera-diffuse-emissive/
// - https://github.com/0beqz/realism-effects/blob/v2-debug/src/ssgi/shader/ssgi.frag
//
// after a hit, it moves the ray this far along the normal away from a surface.
// Helps prevent incorrect intersections when rays bounce off of objects.
// from https://www.shadertoy.com/view/4djSRW
float hash14(vec4 p4) {
p4 = fract(p4 * vec4(0.1031, 0.1030, 0.0973, 0.1099));
p4 += dot(p4, p4.wzxy + 33.33);
return fract((p4.x + p4.y) * (p4.z + p4.w));
}
float randomFloat(inout float state) {
state += 0.06711056;
return 1.0 - hash14(vec4(gl_FragCoord.xy, float(uFrameNo), state));
}
// https://www.pcg-random.org/
// https://jcgt.org/published/0009/03/02/
uint pcg(inout uint seed) {
seed = seed * 747796405u + 2891336453u;
uint word = ((seed >> ((seed >> 28u) + 4u)) ^ seed) * 277803737u;
return (word >> 22u) ^ word;
}
float randomFloat(inout uint state) {
return float(pcg(state)) / 4294967296.0;
}
vec3 randomUnitVector(inout StateType state) {
float z = randomFloat(state) * 2.0 - 1.0;
float a = randomFloat(state) * TWO_PI;
float r = sqrt(1.0 - z * z);
float x = r * cos(a);
float y = r * sin(a);
return vec3(x, y, z);
}
struct RayHitInfo {
bool missed;
vec3 position;
vec3 normal;
vec3 color;
vec3 emissive;
};
//
float getDepth(const in vec2 coords) {
vec2 c = vec2(clamp(coords.x, uBounds.x, uBounds.z), clamp(coords.y, uBounds.y, uBounds.w));
return texture2D(tDepth, c).r;
}
float getThickness(const in vec2 coords) {
vec2 c = vec2(clamp(coords.x, uBounds.x, uBounds.z), clamp(coords.y, uBounds.y, uBounds.w));
return unpackRGBAToDepth(texture2D(tThickness, c));
}
bool isBackground(const in float depth) {
return depth == 1.0;
}
float getViewZ(const in float depth) {
return orthographicDepthToViewZ(depth, uNear, uFar);
return perspectiveDepthToViewZ(depth, uNear, uFar);
}
vec2 viewSpaceToScreenSpace(const vec3 position) {
vec4 projectedCoord = uProjection * vec4(position, 1.0);
projectedCoord.xy /= projectedCoord.w;
// [-1, 1] --> [0, 1] (NDC to screen position)
projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
return projectedCoord.xy;
}
vec2 binarySearch(inout vec3 dir, inout vec3 hitPos) {
float rayHitDepthDifference;
vec2 coords;
dir *= 0.5;
hitPos -= dir;
for (int i = 0; i < dRefineSteps; i++) {
coords = viewSpaceToScreenSpace(hitPos);
float depth = getDepth(coords);
float z = getViewZ(depth);
rayHitDepthDifference = z - hitPos.z;
dir *= 0.5;
if (rayHitDepthDifference >= 0.0) {
hitPos -= dir;
} else {
hitPos += dir;
}
}
coords = viewSpaceToScreenSpace(hitPos);
return coords;
}
float calculateGrowthFactor(float begin, float end, float steps) {
return pow(end / begin, 1.0 / steps);
}
vec2 rayMarch(in vec3 dir, in float thickness, inout vec3 hitPos, out bool missed) {
float rayHitDepthDifference;
vec2 coords;
float begin = float(dFirstStepSize);
dir *= begin;
missed = false;
float gf = calculateGrowthFactor(begin, uRayDistance, float(dSteps));
for (int i = 1; i < dSteps; i++) {
hitPos += dir;
dir *= gf;
coords = viewSpaceToScreenSpace(hitPos);
float depth = getDepth(coords);
float z = getViewZ(depth);
rayHitDepthDifference = z - hitPos.z;
if (thickness == 0.0) {
thickness = max(uMinThickness, (getViewZ(getThickness(coords)) - z) * uThicknessFactor * texture2D(tColor, coords).a);
thickness = uThickness;
}
if (rayHitDepthDifference >= 0.0 && rayHitDepthDifference < thickness) {
if (dRefineSteps == 0) {
return coords;
} else {
return binarySearch(dir, hitPos);
}
}
}
missed = true;
return coords;
}
//
void trace(in vec3 rayPos, in vec3 rayDir, inout RayHitInfo hitInfo) {
vec3 hitPos = vec3(rayPos);
bool missed;
vec2 coords;
coords = rayMarch(rayDir, 0.0, hitPos, missed);
hitInfo.missed = missed;
hitInfo.position = hitPos;
hitInfo.normal = -texture2D(tNormal, coords).rgb;
hitInfo.color = texture2D(tColor, coords).rgb;
hitInfo.emissive = texture2D(tColor, coords).rgb * texture2D(tNormal, coords).a * 2.0;
float depth = getDepth(coords);
if (isBackground(depth)) {
hitInfo.emissive = vec3(0.0);
}
}
vec3 viewPos;
vec3 colorForRay(in vec3 startRayPos, in vec3 startRayDir, inout StateType rngState) {
vec3 ret = vec3(0.0, 0.0, 0.0);
vec3 throughput = vec3(1.0, 1.0, 1.0);
vec3 rayPos = startRayPos;
vec3 rayDir = startRayDir;
RayHitInfo hitInfo;
RayHitInfo prevHitInfo;
for (int bounceIndex = 0; bounceIndex <= dBounces; ++bounceIndex) {
// shoot a ray out into the world
if (bounceIndex == 0) {
vec2 coords = gl_FragCoord.xy / uTexSize;
float depth = getDepth(coords);
hitInfo.missed = false;
hitInfo.position = screenSpaceToViewSpace(vec3(coords, depth), uInvProjection);
hitInfo.normal = -texture2D(tNormal, coords).rgb;
hitInfo.color = texture2D(tShaded, coords).rgb;
hitInfo.emissive = texture2D(tColor, coords).rgb * texture2D(tNormal, coords).a;
// shadow
vec3 directLight = vec3(uAmbientColor);
bool missed;
vec3 hitPos;
for (int i = 0; i < dLightCount; ++i) {
missed = false;
hitPos = viewPos + hitInfo.normal * RayPosNormalNudge;
hitPos += -uLightDirection[i] * (randomFloat(rngState));
rayMarch(-uLightDirection[i] + randomUnitVector(rngState) * uShadowSoftness, uShadowThickness, hitPos, missed);
if (missed) directLight += uLightColor[i];
}
hitInfo.color *= directLight / uLightStrength;
if (hitInfo.normal == vec3(0.0)) {
hitInfo.missed = true;
}
} else {
prevHitInfo = hitInfo;
trace(rayPos, rayDir, hitInfo);
}
// if the ray missed, we are done
if (hitInfo.missed) {
vec3 accIrradiance = vec3(1.0);
if (bounceIndex > 1) {
accIrradiance = uLightStrength;
}
if (bounceIndex > 1) {
accIrradiance = uAmbientColor;
float dotNL;
vec3 irradiance;
for (int i = 0; i < dLightCount; ++i) {
dotNL = saturate(dot(prevHitInfo.normal, -uLightDirection[i]));
irradiance = dotNL * uLightColor[i];
accIrradiance += irradiance;
}
}
ret += prevHitInfo.color * accIrradiance * throughput;
break;
}
// add emissive light
ret += hitInfo.emissive * throughput;
// update the ray position
rayPos = hitInfo.position + hitInfo.normal * RayPosNormalNudge;
// new ray direction from normal oriented cosine weighted hemisphere sample
rayDir = normalize(hitInfo.normal + randomUnitVector(rngState));
if (bounceIndex == 0) {
continue;
}
// update the colorMultiplier.
throughput *= hitInfo.color;
// Russian Roulette
// As the throughput gets smaller, the ray is more likely to get terminated early.
// Survivors have their value boosted to make up for fewer samples being in the average.
{
float p = max(throughput.r, max(throughput.g, throughput.b));
if (randomFloat(rngState) > p)
break;
// Add the energy we 'lose' by randomly terminating paths
throughput *= 1.0 / p;
}
}
// return pixel color
return ret;
}
void main() {
vec2 coords = gl_FragCoord.xy / uTexSize;
float depth = getDepth(coords);
if (isBackground(depth)) {
gl_FragColor = texture2D(tColor, coords);
return;
}
float rngState = 26699.0;
// initialize a random number state based on gl_FragCoord and uFrameNo
uint rngState = uint(uint(gl_FragCoord.x) * 1973u + uint(gl_FragCoord.y) * 9277u + uint(uFrameNo) * 26699u) | 1u;
vec3 cameraPos = vec3(0.0, 0.0, 0.0);
viewPos = screenSpaceToViewSpace(vec3(coords, depth), uInvProjection);
vec3 rayDir = normalize(viewPos);
// raytrace for this pixel
vec3 color = vec3(0.0, 0.0, 0.0);
for (int index = 0; index < int(dRendersPerFrame); ++index) {
color += colorForRay(cameraPos, rayDir, rngState) / float(dRendersPerFrame);
}
// average the frames together
vec4 lastFrameColor = texture2D(tAccumulate, coords);
float blend = (uFrameNo < 1 || lastFrameColor.a == 0.0) ? 1.0 : 1.0 / (1.0 + (1.0 / lastFrameColor.a));
color = mix(lastFrameColor.rgb, color, blend);
// show the result
gl_FragColor = vec4(color, blend);
}
`;