{"version":3,"file":"VolumeShader.cjs","sources":["../../src/shaders/VolumeShader.ts"],"sourcesContent":["import { Vector2, Vector3 } from 'three'\n\n/**\n * Shaders to render 3D volumes using raycasting.\n * The applied techniques are based on similar implementations in the Visvis and Vispy projects.\n * This is not the only approach, therefore it's marked 1.\n */\n\nexport const VolumeRenderShader1 = {\n  uniforms: {\n    u_size: { value: new Vector3(1, 1, 1) },\n    u_renderstyle: { value: 0 },\n    u_renderthreshold: { value: 0.5 },\n    u_clim: { value: new Vector2(1, 1) },\n    u_data: { value: null },\n    u_cmdata: { value: null },\n  },\n  vertexShader: [\n    '\t\tvarying vec4 v_nearpos;',\n    '\t\tvarying vec4 v_farpos;',\n    '\t\tvarying vec3 v_position;',\n\n    '\t\tvoid main() {',\n    // Prepare transforms to map to \"camera view\". See also:\n    // https://threejs.org/docs/#api/renderers/webgl/WebGLProgram\n    '\t\t\t\tmat4 viewtransformf = modelViewMatrix;',\n    '\t\t\t\tmat4 viewtransformi = inverse(modelViewMatrix);',\n\n    // Project local vertex coordinate to camera position. Then do a step\n    // backward (in cam coords) to the near clipping plane, and project back. Do\n    // the same for the far clipping plane. This gives us all the information we\n    // need to calculate the ray and truncate it to the viewing cone.\n    '\t\t\t\tvec4 position4 = vec4(position, 1.0);',\n    '\t\t\t\tvec4 pos_in_cam = viewtransformf * position4;',\n\n    // Intersection of ray and near clipping plane (z = -1 in clip coords)\n    '\t\t\t\tpos_in_cam.z = -pos_in_cam.w;',\n    '\t\t\t\tv_nearpos = viewtransformi * pos_in_cam;',\n\n    // Intersection of ray and far clipping plane (z = +1 in clip coords)\n    '\t\t\t\tpos_in_cam.z = pos_in_cam.w;',\n    '\t\t\t\tv_farpos = viewtransformi * pos_in_cam;',\n\n    // Set varyings and output pos\n    '\t\t\t\tv_position = position;',\n    '\t\t\t\tgl_Position = projectionMatrix * viewMatrix * modelMatrix * position4;',\n    '\t\t}',\n  ].join('\\n'),\n  fragmentShader: [\n    '\t\tprecision highp float;',\n    '\t\tprecision mediump sampler3D;',\n\n    '\t\tuniform vec3 u_size;',\n    '\t\tuniform int u_renderstyle;',\n    '\t\tuniform float u_renderthreshold;',\n    '\t\tuniform vec2 u_clim;',\n\n    '\t\tuniform sampler3D u_data;',\n    '\t\tuniform sampler2D u_cmdata;',\n\n    '\t\tvarying vec3 v_position;',\n    '\t\tvarying vec4 v_nearpos;',\n    '\t\tvarying vec4 v_farpos;',\n\n    // The maximum distance through our rendering volume is sqrt(3).\n    '\t\tconst int MAX_STEPS = 887;\t// 887 for 512^3, 1774 for 1024^3',\n    '\t\tconst int REFINEMENT_STEPS = 4;',\n    '\t\tconst float relative_step_size = 1.0;',\n    '\t\tconst vec4 ambient_color = vec4(0.2, 0.4, 0.2, 1.0);',\n    '\t\tconst vec4 diffuse_color = vec4(0.8, 0.2, 0.2, 1.0);',\n    '\t\tconst vec4 specular_color = vec4(1.0, 1.0, 1.0, 1.0);',\n    '\t\tconst float shininess = 40.0;',\n\n    '\t\tvoid cast_mip(vec3 start_loc, vec3 step, int nsteps, vec3 view_ray);',\n    '\t\tvoid cast_iso(vec3 start_loc, vec3 step, int nsteps, vec3 view_ray);',\n\n    '\t\tfloat sample1(vec3 texcoords);',\n    '\t\tvec4 apply_colormap(float val);',\n    '\t\tvec4 add_lighting(float val, vec3 loc, vec3 step, vec3 view_ray);',\n\n    '\t\tvoid main() {',\n    // Normalize clipping plane info\n    '\t\t\t\tvec3 farpos = v_farpos.xyz / v_farpos.w;',\n    '\t\t\t\tvec3 nearpos = v_nearpos.xyz / v_nearpos.w;',\n\n    // Calculate unit vector pointing in the view direction through this fragment.\n    '\t\t\t\tvec3 view_ray = normalize(nearpos.xyz - farpos.xyz);',\n\n    // Compute the (negative) distance to the front surface or near clipping plane.\n    // v_position is the back face of the cuboid, so the initial distance calculated in the dot\n    // product below is the distance from near clip plane to the back of the cuboid\n    '\t\t\t\tfloat distance = dot(nearpos - v_position, view_ray);',\n    '\t\t\t\tdistance = max(distance, min((-0.5 - v_position.x) / view_ray.x,',\n    '\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t(u_size.x - 0.5 - v_position.x) / view_ray.x));',\n    '\t\t\t\tdistance = max(distance, min((-0.5 - v_position.y) / view_ray.y,',\n    '\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t(u_size.y - 0.5 - v_position.y) / view_ray.y));',\n    '\t\t\t\tdistance = max(distance, min((-0.5 - v_position.z) / view_ray.z,',\n    '\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t(u_size.z - 0.5 - v_position.z) / view_ray.z));',\n\n    // Now we have the starting position on the front surface\n    '\t\t\t\tvec3 front = v_position + view_ray * distance;',\n\n    // Decide how many steps to take\n    '\t\t\t\tint nsteps = int(-distance / relative_step_size + 0.5);',\n    '\t\t\t\tif ( nsteps < 1 )',\n    '\t\t\t\t\t\tdiscard;',\n\n    // Get starting location and step vector in texture coordinates\n    '\t\t\t\tvec3 step = ((v_position - front) / u_size) / float(nsteps);',\n    '\t\t\t\tvec3 start_loc = front / u_size;',\n\n    // For testing: show the number of steps. This helps to establish\n    // whether the rays are correctly oriented\n    //'gl_FragColor = vec4(0.0, float(nsteps) / 1.0 / u_size.x, 1.0, 1.0);',\n    //'return;',\n\n    '\t\t\t\tif (u_renderstyle == 0)',\n    '\t\t\t\t\t\tcast_mip(start_loc, step, nsteps, view_ray);',\n    '\t\t\t\telse if (u_renderstyle == 1)',\n    '\t\t\t\t\t\tcast_iso(start_loc, step, nsteps, view_ray);',\n\n    '\t\t\t\tif (gl_FragColor.a < 0.05)',\n    '\t\t\t\t\t\tdiscard;',\n    '\t\t}',\n\n    '\t\tfloat sample1(vec3 texcoords) {',\n    '\t\t\t\t/* Sample float value from a 3D texture. Assumes intensity data. */',\n    '\t\t\t\treturn texture(u_data, texcoords.xyz).r;',\n    '\t\t}',\n\n    '\t\tvec4 apply_colormap(float val) {',\n    '\t\t\t\tval = (val - u_clim[0]) / (u_clim[1] - u_clim[0]);',\n    '\t\t\t\treturn texture2D(u_cmdata, vec2(val, 0.5));',\n    '\t\t}',\n\n    '\t\tvoid cast_mip(vec3 start_loc, vec3 step, int nsteps, vec3 view_ray) {',\n\n    '\t\t\t\tfloat max_val = -1e6;',\n    '\t\t\t\tint max_i = 100;',\n    '\t\t\t\tvec3 loc = start_loc;',\n\n    // Enter the raycasting loop. In WebGL 1 the loop index cannot be compared with\n    // non-constant expression. So we use a hard-coded max, and an additional condition\n    // inside the loop.\n    '\t\t\t\tfor (int iter=0; iter<MAX_STEPS; iter++) {',\n    '\t\t\t\t\t\tif (iter >= nsteps)',\n    '\t\t\t\t\t\t\t\tbreak;',\n    // Sample from the 3D texture\n    '\t\t\t\t\t\tfloat val = sample1(loc);',\n    // Apply MIP operation\n    '\t\t\t\t\t\tif (val > max_val) {',\n    '\t\t\t\t\t\t\t\tmax_val = val;',\n    '\t\t\t\t\t\t\t\tmax_i = iter;',\n    '\t\t\t\t\t\t}',\n    // Advance location deeper into the volume\n    '\t\t\t\t\t\tloc += step;',\n    '\t\t\t\t}',\n\n    // Refine location, gives crispier images\n    '\t\t\t\tvec3 iloc = start_loc + step * (float(max_i) - 0.5);',\n    '\t\t\t\tvec3 istep = step / float(REFINEMENT_STEPS);',\n    '\t\t\t\tfor (int i=0; i<REFINEMENT_STEPS; i++) {',\n    '\t\t\t\t\t\tmax_val = max(max_val, sample1(iloc));',\n    '\t\t\t\t\t\tiloc += istep;',\n    '\t\t\t\t}',\n\n    // Resolve final color\n    '\t\t\t\tgl_FragColor = apply_colormap(max_val);',\n    '\t\t}',\n\n    '\t\tvoid cast_iso(vec3 start_loc, vec3 step, int nsteps, vec3 view_ray) {',\n\n    '\t\t\t\tgl_FragColor = vec4(0.0);\t// init transparent',\n    '\t\t\t\tvec4 color3 = vec4(0.0);\t// final color',\n    '\t\t\t\tvec3 dstep = 1.5 / u_size;\t// step to sample derivative',\n    '\t\t\t\tvec3 loc = start_loc;',\n\n    '\t\t\t\tfloat low_threshold = u_renderthreshold - 0.02 * (u_clim[1] - u_clim[0]);',\n\n    // Enter the raycasting loop. In WebGL 1 the loop index cannot be compared with\n    // non-constant expression. So we use a hard-coded max, and an additional condition\n    // inside the loop.\n    '\t\t\t\tfor (int iter=0; iter<MAX_STEPS; iter++) {',\n    '\t\t\t\t\t\tif (iter >= nsteps)',\n    '\t\t\t\t\t\t\t\tbreak;',\n\n    // Sample from the 3D texture\n    '\t\t\t\t\t\tfloat val = sample1(loc);',\n\n    '\t\t\t\t\t\tif (val > low_threshold) {',\n    // Take the last interval in smaller steps\n    '\t\t\t\t\t\t\t\tvec3 iloc = loc - 0.5 * step;',\n    '\t\t\t\t\t\t\t\tvec3 istep = step / float(REFINEMENT_STEPS);',\n    '\t\t\t\t\t\t\t\tfor (int i=0; i<REFINEMENT_STEPS; i++) {',\n    '\t\t\t\t\t\t\t\t\t\tval = sample1(iloc);',\n    '\t\t\t\t\t\t\t\t\t\tif (val > u_renderthreshold) {',\n    '\t\t\t\t\t\t\t\t\t\t\t\tgl_FragColor = add_lighting(val, iloc, dstep, view_ray);',\n    '\t\t\t\t\t\t\t\t\t\t\t\treturn;',\n    '\t\t\t\t\t\t\t\t\t\t}',\n    '\t\t\t\t\t\t\t\t\t\tiloc += istep;',\n    '\t\t\t\t\t\t\t\t}',\n    '\t\t\t\t\t\t}',\n\n    // Advance location deeper into the volume\n    '\t\t\t\t\t\tloc += step;',\n    '\t\t\t\t}',\n    '\t\t}',\n\n    '\t\tvec4 add_lighting(float val, vec3 loc, vec3 step, vec3 view_ray)',\n    '\t\t{',\n    // Calculate color by incorporating lighting\n\n    // View direction\n    '\t\t\t\tvec3 V = normalize(view_ray);',\n\n    // calculate normal vector from gradient\n    '\t\t\t\tvec3 N;',\n    '\t\t\t\tfloat val1, val2;',\n    '\t\t\t\tval1 = sample1(loc + vec3(-step[0], 0.0, 0.0));',\n    '\t\t\t\tval2 = sample1(loc + vec3(+step[0], 0.0, 0.0));',\n    '\t\t\t\tN[0] = val1 - val2;',\n    '\t\t\t\tval = max(max(val1, val2), val);',\n    '\t\t\t\tval1 = sample1(loc + vec3(0.0, -step[1], 0.0));',\n    '\t\t\t\tval2 = sample1(loc + vec3(0.0, +step[1], 0.0));',\n    '\t\t\t\tN[1] = val1 - val2;',\n    '\t\t\t\tval = max(max(val1, val2), val);',\n    '\t\t\t\tval1 = sample1(loc + vec3(0.0, 0.0, -step[2]));',\n    '\t\t\t\tval2 = sample1(loc + vec3(0.0, 0.0, +step[2]));',\n    '\t\t\t\tN[2] = val1 - val2;',\n    '\t\t\t\tval = max(max(val1, val2), val);',\n\n    '\t\t\t\tfloat gm = length(N); // gradient magnitude',\n    '\t\t\t\tN = normalize(N);',\n\n    // Flip normal so it points towards viewer\n    '\t\t\t\tfloat Nselect = float(dot(N, V) > 0.0);',\n    '\t\t\t\tN = (2.0 * Nselect - 1.0) * N;\t// ==\tNselect * N - (1.0-Nselect)*N;',\n\n    // Init colors\n    '\t\t\t\tvec4 ambient_color = vec4(0.0, 0.0, 0.0, 0.0);',\n    '\t\t\t\tvec4 diffuse_color = vec4(0.0, 0.0, 0.0, 0.0);',\n    '\t\t\t\tvec4 specular_color = vec4(0.0, 0.0, 0.0, 0.0);',\n\n    // note: could allow multiple lights\n    '\t\t\t\tfor (int i=0; i<1; i++)',\n    '\t\t\t\t{',\n    // Get light direction (make sure to prevent zero devision)\n    '\t\t\t\t\t\tvec3 L = normalize(view_ray);\t//lightDirs[i];',\n    '\t\t\t\t\t\tfloat lightEnabled = float( length(L) > 0.0 );',\n    '\t\t\t\t\t\tL = normalize(L + (1.0 - lightEnabled));',\n\n    // Calculate lighting properties\n    '\t\t\t\t\t\tfloat lambertTerm = clamp(dot(N, L), 0.0, 1.0);',\n    '\t\t\t\t\t\tvec3 H = normalize(L+V); // Halfway vector',\n    '\t\t\t\t\t\tfloat specularTerm = pow(max(dot(H, N), 0.0), shininess);',\n\n    // Calculate mask\n    '\t\t\t\t\t\tfloat mask1 = lightEnabled;',\n\n    // Calculate colors\n    '\t\t\t\t\t\tambient_color +=\tmask1 * ambient_color;\t// * gl_LightSource[i].ambient;',\n    '\t\t\t\t\t\tdiffuse_color +=\tmask1 * lambertTerm;',\n    '\t\t\t\t\t\tspecular_color += mask1 * specularTerm * specular_color;',\n    '\t\t\t\t}',\n\n    // Calculate final color by componing different components\n    '\t\t\t\tvec4 final_color;',\n    '\t\t\t\tvec4 color = apply_colormap(val);',\n    '\t\t\t\tfinal_color = color * (ambient_color + diffuse_color) + specular_color;',\n    '\t\t\t\tfinal_color.a = color.a;',\n    '\t\t\t\treturn final_color;',\n    '\t\t}',\n  ].join('\\n'),\n}\n"],"names":["Vector3","Vector2"],"mappings":"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"}