2025-01-25 19:20:05 +00:00

141 lines
5.1 KiB
JavaScript

export const vertexShader = `
// Attributes.
in vec3 position;
// Uniforms.
uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 cameraPosition;
// Output.
out vec3 vOrigin; // Output ray origin.
out vec3 vDirection; // Output ray direction.
void main() {
// Compute the ray origin in model space.
vOrigin = vec3(inverse(modelMatrix) * vec4(cameraPosition, 1.0)).xyz;
// Compute ray direction in model space.
vDirection = position - vOrigin;
// Compute vertex position in clip space.
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
export const fragmentShader = `
precision highp sampler3D; // Precision for 3D texture sampling.
precision highp float; // Precision for floating point numbers.
uniform sampler3D dataTexture; // Sampler for the volume data texture.
// uniform sampler2D colorTexture; // Sampler for the color palette texture.
uniform float samplingRate; // The sampling rate.
uniform float threshold; // Threshold to use for isosurface-style rendering.
uniform float alphaScale; // Scaling of the color alpha value.
uniform bool invertColor; // Option to invert the color palette.
in vec3 vOrigin; // The interpolated ray origin from the vertex shader.
in vec3 vDirection; // The interpolated ray direction from the vertex shader.
out vec4 frag_color; // Output fragment color.
// Sampling of the volume data texture.
float sampleData(vec3 coord) {
return texture(dataTexture, coord).x;
}
// Sampling of the color palette texture.
vec4 sampleColor(float value) {
// In case the color palette should be inverted, invert the texture coordinate to sample the color texture.
float x = invertColor ? value : 1.0 - value;
// return texture(colorTexture, vec2(x, 0.5));
return vec4(x, x, x, 1.0);
}
// Intersection of a ray and an axis-aligned bounding box.
// Returns the intersections as the minimum and maximum distance along the ray direction.
vec2 intersectAABB(vec3 rayOrigin, vec3 rayDir, vec3 boxMin, vec3 boxMax) {
vec3 tMin = (boxMin - rayOrigin) / rayDir;
vec3 tMax = (boxMax - rayOrigin) / rayDir;
vec3 t1 = min(tMin, tMax);
vec3 t2 = max(tMin, tMax);
float tNear = max(max(t1.x, t1.y), t1.z);
float tFar = min(min(t2.x, t2.y), t2.z);
return vec2(tNear, tFar);
}
// Volume sampling and composition.
// Note that the code is inserted based on the selected algorithm in the user interface.
vec4 compose(vec4 color, vec3 entryPoint, vec3 rayDir, float samples, float tStart, float tEnd, float tIncr) {
// Composition of samples using maximum intensity projection.
// Loop through all samples along the ray.
float density = 0.0;
for (float i = 0.0; i < samples; i += 1.0) {
// Determine the sampling position.
float t = tStart + tIncr * i; // Current distance along ray.
vec3 p = entryPoint + rayDir * t; // Current position.
// Sample the volume data at the current position.
float value = sampleData(p);
// Keep track of the maximum value.
if (value > density) {
// Store the value if it is greater than the previous values.
density = value;
}
// Early exit the loop when the maximum possible value is found or the exit point is reached.
if (density >= 1.0 || t > tEnd) {
break;
}
}
// Convert the found value to a color by sampling the color palette texture.
color.rgb = sampleColor(density).rgb;
// Modify the alpha value of the color to make lower values more transparent.
color.a = alphaScale * (invertColor ? 1.0 - density : density);
// Return the color for the ray.
return color;
}
void main() {
// Determine the intersection of the ray and the box.
vec3 rayDir = normalize(vDirection);
vec3 aabbmin = vec3(-0.5);
vec3 aabbmax = vec3(0.5);
vec2 intersection = intersectAABB(vOrigin, rayDir, aabbmin, aabbmax);
// Initialize the fragment color.
vec4 color = vec4(0.0);
// Check if the intersection is valid, i.e., if the near distance is smaller than the far distance.
if (intersection.x <= intersection.y) {
// Clamp the near intersection distance when the camera is inside the box so we do not start sampling behind the camera.
intersection.x = max(intersection.x, 0.0);
// Compute the entry and exit points for the ray.
vec3 entryPoint = vOrigin + rayDir * intersection.x;
vec3 exitPoint = vOrigin + rayDir * intersection.y;
// Determine the sampling rate and step size.
// Entry Exit Align Corner sampling as described in
// Volume Raycasting Sampling Revisited by Steneteg et al. 2019
vec3 dimensions = vec3(textureSize(dataTexture, 0));
vec3 entryToExit = exitPoint - entryPoint;
float samples = ceil(samplingRate * length(entryToExit * (dimensions - vec3(1.0))));
float tEnd = length(entryToExit);
float tIncr = tEnd / samples;
float tStart = 0.5 * tIncr;
// Determine the entry point in texture space to simplify texture sampling.
vec3 texEntry = (entryPoint - aabbmin) / (aabbmax - aabbmin);
// Sample the volume along the ray and convert samples to color.
color = compose(color, texEntry, rayDir, samples, tStart, tEnd, tIncr);
}
// Return the fragment color.
frag_color = color;
}`;