UNPKG

molstar

Version:

A comprehensive macromolecular library.

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