Repository navigation
Post processing
postprocessing/composer.mojo. An EffectComposer runs passes over the light a frame leaves in a render target and resolves it once at the end. three.js: EffectComposer and the passes under examples/jsm/postprocessing/.

examples/bloom.mojo draws this picture.
var composer = EffectComposer()
composer.add_pass(render_pass())
composer.add_pass(bloom_pass(1.5, 0.4, 0.85))
composer.add_pass(output_pass())
var image = composer.render(renderer, scene, assets, camera)The frame starts cleared to the renderer's background. A render pass draws the scene into it. Every other pass reads the light and writes it back. The frame is resolved through no curve of its own. An output_pass is where the renderer's tone mapping curve is applied, as three.js applies it in an OutputPass once a composer is in use.
To run the passes on the GPU, give the composer to a GpuComposer. Most passes then run as kernels, and the frame stays on the device between them. See GPU backend.
| Builder | three.js | Meaning |
|---|---|---|
render_pass() |
RenderPass |
Draw the scene, clearing first. Through the renderer's antialias when that is on. |
copy_pass(opacity=1) |
ShaderPass(CopyShader) |
Scale every channel of every pixel, alpha included. |
blur_pass(spread=1) |
HorizontalBlurShader then VerticalBlurShader
|
Nine taps across, then nine down, spread pixels apart. |
bloom_pass(strength=1, radius=0, threshold=0) |
UnrealBloomPass |
The light above threshold blurred at five halved sizes, weighted by radius, scaled by strength and added back times its own alpha. |
film_pass(intensity=0.5, grayscale=False) |
FilmPass |
Grain from a hash of the pixel and the time, and gray if asked. |
dot_screen_pass(center, angle, scale=1) |
DotScreenPass |
A halftone of dots over a 256-texel grid. |
sepia_pass(amount=1) |
SepiaShader |
An old photograph's tint. |
vignette_pass(offset=1, darkness=1) |
VignetteShader |
Darkened corners. |
luminosity_pass() |
LuminosityShader |
Gray by luminance. |
afterimage_pass(damp=0.96) |
AfterimagePass |
The last frame fading under this one. |
output_pass() |
OutputPass |
The renderer's tone mapping curve, on every pixel that holds light. |
fxaa_pass() |
FXAAPass |
Smooth jagged edges by their luminance. |
smaa_pass() |
SMAAPass |
Smooth jagged edges by their shape, in three stages. |
ssaa_render_pass(sample_level=4, unbiased=True) |
SSAARenderPass |
Draw the scene once per jittered sample and average the samples. |
taa_render_pass(sample_level=0, accumulate=False) |
TAARenderPass |
Draw jittered samples, and accumulate 32 of them over frames when accumulate is on. |
traa_pass(depth_threshold=0.0005, edge_depth_diff=0.001, max_velocity_length=128) |
TRAANode |
Draw the scene jittered, and mix it into the last frames where each pixel was. |
ssao_pass(kernel_radius=8 m, min_distance=0.005, max_distance=0.1) |
SSAOPass |
Draw the scene, and darken it where its surfaces are hemmed in. |
sao_pass(intensity=0.18, scale=1, kernel_radius=100, blur=True) |
SAOPass |
Darken the frame by scalable ambient occlusion. |
ssr_pass(opacity=0.5, max_distance=180 m, thickness=0.018 m) |
SSRPass |
Draw the scene, and lay what each surface reflects over it. |
outline_pass(selection, edge_strength=3, edge_thickness=1) |
OutlinePass |
Draw a glowing edge around the objects on the selected layers. |
bokeh_pass(focus=1 m, aperture=0.025, max_blur=1) |
BokehPass |
Blur each pixel by its distance from the focus. |
bokeh2_pass(settings) |
ShaderPass with BokehShader2
|
Blur by depth through a lens's rings of taps. |
dof_mipmap_pass(focus=1, max_blur=1) |
ShaderPass with DOFMipMapShader
|
Read each pixel from a mip level set by its depth. |
classic_bloom_pass(strength=1, kernel_size=25, sigma=4) |
BloomPass |
Blur the frame by a Gaussian and add it back. |
glitch_pass(size=64, seed=0) |
GlitchPass |
Shift the channels, tear the frame and add snow, at random moments. |
halftone_pass(radius=4) |
HalftonePass |
Redraw each channel as a grid of dots. |
mask_pass(selection, inverse=False) |
MaskPass |
Let the passes after it change only the pixels that the selected objects cover. |
clear_mask_pass() |
ClearMaskPass |
Let the passes after it change every pixel again. |
clear_pass(color=transparent black) |
ClearPass |
Clear the light, the depth and the stencil. |
texture_pass(texture, opacity=1) |
TexturePass |
Draw a texture over the frame. |
lut_pass(lut, intensity=1) |
LUTPass |
Grade the frame through a color lookup table. |
render_pixelated_pass(pixel_size=6, normal_edge_strength=0.3, depth_edge_strength=0.4) |
RenderPixelatedPass |
Draw the scene small and show it in blocks, with its edges drawn. |
gtao_pass(radius=0.25 m, blend_intensity=1) |
GTAOPass |
Darken the frame by ground-truth ambient occlusion. |
render_transition_pass(first, second, texture=NO_TEXTURE, mix_ratio=0) |
RenderTransitionPass |
Draw two sets of layers and mix them, evenly or by a texture. |
cube_texture_pass(cube, opacity=1) |
CubeTexturePass |
Draw a cube texture as the camera sees it. |
save_pass() |
SavePass |
Keep a copy of the frame. |
shader_pass(program, input="tDiffuse") |
ShaderPass |
Run a node program over the screen. |
effect_pass(effect) |
ShaderPass with one of eighteen shaders |
Run a screen shader. Each shader also has a builder of its own. |
god_rays_pass(sun, intensity=0.69, fake_sun=False) |
The god-rays example | Lay rays from a sun over the frame. |
Each builder returns a Pass. A Pass has a kind, an enabled flag and every setting any kind reads. The effect passes read strength, radius, threshold, offset, scale, angle, center, grayscale and time. The SSAA and TAA passes read sample_level, unbiased, accumulate and accumulate_index. The screen-space passes read ssao, sao, ssr and outline. See Screen-space passes.
The bokeh, glitch, halftone and mask passes read bokeh, glitch, halftone and mask. The clear pass reads clear_color. The texture pass reads texture and strength. The LUT pass reads lut and strength. See More passes.
The pixelated, GTAO, transition, shader, shader effect and god-rays passes read pixelated, gtao, transition, shader, effect and god_rays. The transition pass also reads first, second and texture. The cube texture pass reads cube and strength. See Scene, GTAO and shader passes and Shader effects.
Change a setting after the pass is added, as three.js changes a uniform. A pass that is not enabled is skipped.
The defaults are three.js's. The dot screen's angle is an Angle. A bare number does not compile. tests/compile_fail/ proves it.
render_pass(override_material, clear_color, clear_alpha) takes three.js's RenderPass arguments. The override material draws every object in the place of its own, as three.js puts it on the scene. Renderer.override_material carries it for the draw. The clear color and alpha take the place of the renderer's background, and a scene's own background still wins, as in three.js.
The pass has clear on and clear_depth off, as three.js's has. With clear off, the pass draws over the frame that it is given. That draw is not supersampled, because a supersampled draw starts from a fresh target. With clear_depth on, the depth is cleared first.
| Member | three.js | Meaning |
|---|---|---|
add_pass(pass) |
addPass |
Append a pass. |
insert_pass(pass, index) |
insertPass |
Put a pass before the one at index. |
remove_pass(index) |
removePass |
Take a pass out. |
pass_count() -> Int |
How many passes there are. | |
reset() |
reset |
Forget what every afterimage pass saw and what every TAA pass accumulated. |
saved_image(index) -> List[FloatColor] |
SavePass.renderTarget |
The copy of the frame that the save pass at index kept. Empty before the pass runs. |
render(renderer, scene, assets, camera, delta_time=0) -> Framebuffer |
render |
Run every enabled pass in order and return the image. |
run_step(index, frame, renderer, scene, assets, camera, delta_time) |
Run one pass on a frame, less the mask's bookkeeping. It fits the memories first, and refuses an index that names no pass. The GPU composer calls it for a pass with no kernel. |
delta_time is how many seconds passed since the last frame. It advances each film pass's grain and each outline pass's pulse. Zero holds them still.
PassKind is a type. is_valid names the kinds. check_pass refuses any other kind. It refuses a setting that is not finite. It refuses a strength, radius, threshold, offset or scale that is negative. It refuses a bloom radius above one and an afterimage damp above one.
check_pass also refuses a sample level outside zero through five. three.js clamps the level; this port refuses it. It refuses an accumulate index outside minus one through 32. jitter_offsets refuses a level outside zero through five. JitteredCamera refuses an offset that is not finite and an image size that is not positive.
add_pass and insert_pass call check_pass. render calls it again on every pass, so a setting changed after the pass was added is checked too. render also refuses a frame time that is negative or not finite.
A blur, a bloom, a copy and an afterimage work on the premultiplied light, where a sum is a sum. A color transform works on the straight color of each pixel and premultiplies it back, as a shader sees a straight texel. The sepia, the gray, the dot screen, the vignette, the grain and the curve are color transforms. Every pass keeps alpha but the copy, which scales it as three.js's CopyShader scales the whole texel.
The exceptions in More passes follow their shaders. The bokeh and the halftone are opaque. The glitch adds to alpha, and the texture is drawn over the alpha too.
A tap past the edge of the frame reads the edge pixel, as a clamped texture does. A pixel that holds data rather than light, a normal or a depth, is not tone mapped by the output pass. The other passes treat it as light.
The bloom's five levels are halved as three.js halves them, rounded up, never below one pixel. Its blur kernels are three, five, seven, nine and eleven taps to each side, each with a sigma of its own width. three.js adds the glow with AdditiveBlending, which multiplies the color by the glow's alpha. That alpha is the strength times the level weights, so twice the strength gives four times the glow. The bloom keeps the frame's alpha. The dot screen measures its pattern over a fixed 256 by 256 grid, as three.js's pass sets tSize once.
postprocessing/antialiasing.mojo and postprocessing/traa.mojo. Five passes smooth jagged edges. FXAA and SMAA work on the finished frame. SSAA and TAA draw the scene more than once, each time moved by a fraction of a pixel. TRAA draws it once a frame and reuses the frames before.
var composer = EffectComposer()
composer.add_pass(render_pass())
composer.add_pass(output_pass())
composer.add_pass(smaa_pass())
var image = composer.render(renderer, scene, assets, camera)Put fxaa_pass and smaa_pass after the output_pass, as the three.js examples do. Then they see the light that the curve leaves. Put ssaa_render_pass, taa_render_pass and traa_pass where a render_pass goes. They replace the frame.
fxaa_pass is three.js's FXAAShader, line for line. It reads the luminance of each pixel and its eight neighbors, with the weights 0.3, 0.59 and 0.11. A pixel whose neighborhood spans less than 0.0312, or less than 0.063 of the brightest, is kept.
Otherwise the pass finds the edge through the pixel. It walks along the edge in six steps of 1, 1.5, 2, 2, 2 and 4 pixels. It guesses 8 more when the edge does not end. The pixel's texture read moves across the edge by the larger of two factors. The subpixel factor grows with the difference between the pixel and its neighborhood. The edge factor grows as the pixel comes nearer to an end of the edge.
smaa_pass is three.js's SMAAPass in its three stages. Each stage is a function that you can call on its own.
-
smaa_edgesmarks an edge on the left or the top of a pixel. The step to that neighbor must be at least 0.1 in some channel. It must also be at least half the largest step to the four neighbors and to the pixels two to the left and two above. -
smaa_weightswalks each edge to its two ends and reads the edges that cross it there.smaa_areaturns that pattern and the two distances into two weights. A vertical edge is a horizontal edge on the transposed map. -
smaa_blendmixes each pixel toward the neighbor with the largest weight. It raises red, green and blue to 2.2 first and lowers them after, as three.js's WebGL port does.
ssaa_render_pass is three.js's SSAARenderPass. It draws the scene once per sample of three.js's jitter patterns: 1, 2, 4, 8, 16 or 32 samples for levels zero through five. Each sample is cleared to the renderer's background and drawn as a render pass draws. The pass adds each sample with a weight of one over the count. With unbiased on, the weights spread evenly over a thirty-second about that, as three.js spreads them.
taa_render_pass is three.js's TAARenderPass. With accumulate off, it is an SSAA pass of its level. With accumulate on, the first frame is supersampled and held. Each frame then draws two to the level of the 32 samples, and shows their sum over the held frame. When all 32 are in, the pass shows their average and draws no more.
Set accumulate_index to minus one, or call reset, when the scene or the camera moves. Then the pass starts again. A frame of another size also starts it again.
JitteredCamera does the jitter. It wraps any camera and moves its projection, as three.js's setViewOffset moves it. An offset of one pixel right moves the picture one pixel left. jitter_offsets(level) returns three.js's patterns in pixels.
A pixel of an SSAA frame is data only if it is data in every sample. Its depth is the nearest. A TAA frame keeps the data flags and the depths of its held frame.
traa_pass is three.js r186's TRAANode, with the scene pass that it reads. Each frame, the pass moves the camera by the next of 32 Halton offsets, less a half pixel. It draws the scene with a velocity attachment. The velocity is measured with the projection before the move, through Renderer.set_velocity_projection, so the move is not motion.
Then each pixel mixes the frame into the history, the pass's last result, as traa_pixel does:
- The 3 by 3 neighbors give the closest depth and the farthest depth. The velocity is read at the closest.
- The history is read where the velocity says that the pixel was. The last frame's depth there is moved into this frame's view.
- The history is kept if its place is on the image and the surface was not uncovered. A surface is uncovered when it is more than
depth_thresholdbehind the last frame's depth. An edge keeps the history too. At an edge, the neighbors' depths differ by more thanedge_depth_diff. - The history is clipped to the box of the neighbors' mean and deviation. This is three.js's variance clipping.
- The new frame takes at least 5 percent. It takes more for a velocity that is a fraction of a pixel and for a fast one. It takes all where the history is not kept.
flicker_reductionweighs each side down by its brightness.
The first frame's history is the frame itself. A renderer of another size starts the history again. The pass keeps its history in traa, a TraaSettings.
TRAA differs from three.js in two ways. A neighbor past the edge of the image is the edge pixel, as WebGPU's robust textureLoad gives it. The first frame's last depth is zero, as a fresh WebGPU depth texture holds.
The edges and the blend are three.js's arithmetic. The weights stage computes what three.js reads from two textures, and the results differ a little.
- The search. three.js walks two pixels at a time with a bilinear read and decodes the read through a 66 by 33 search texture. This port walks one pixel at a time and stops where SMAA stops: at a crossing edge, or where the edge ends. The walk reaches 15 pixels to the left and 16 to the right, as SMAA's eight steps reach.
-
The area. three.js reads the area from a 160 by 560 texture. SMAA's
AreaTex.pyscript fills that texture. This port computes the area with the script'sarea,areaorthoandsmoothareafunctions, for SMAA 1x with no subpixel offset. The texture holds the area at squared distances and interpolates between them. This port uses the exact distance, and it does not round the area to bytes. - The crossing rows. three.js's WebGL port reads the crossing edges at the right end one row lower than SMAA does. This port reads both ends at the rows SMAA reads.
- The texel. Every pass reads the texel as stored, premultiplied. For an opaque pixel that is the straight color.
The diagonal search and the corner detection of full SMAA are not in three.js, and they are not ported.
postprocessing/screen_space.mojo. Four passes read the depth that a render leaves beside its light. SSAO and SAO darken the frame where surfaces are hemmed in. SSR lays reflections over the frame. The outline pass draws a glowing edge around chosen objects.
var composer = EffectComposer()
composer.add_pass(ssao_pass(Length(0.5, METER)))
composer.add_pass(output_pass())
var image = composer.render(renderer, scene, assets, camera)Put ssao_pass and ssr_pass where a render_pass goes. They draw the frame themselves, as three.js's passes do. Put sao_pass and outline_pass after a render_pass. They draw the depth they need, and change the frame that they are given. Put all four before the output_pass.
Each pass keeps its settings in a field of Pass: ssao, sao, ssr or outline. The fields have three.js's names and defaults. Change a field after the pass is added, as three.js changes a property.
DepthView reads a render target's depth through the camera that drew it. The target keeps NDC depth, from minus one to one. DepthView keeps the window depth, from zero to one, as a three.js depth texture holds it. A pixel where nothing was drawn has a depth of one.
A target drawn with a logarithmic or a reversed depth keeps its mode in depth_mode. DepthView takes that mode and reads the stored depth back into the same window depth. So SSAO, SAO, SSR and the bokeh pass work in every mode. The outline pass and the SSAA pass compare depths in the mode of the frame.
-
position(u, v, depth)is three.js'sgetViewPosition. It returns the point in the camera's space. -
view_z(depth)isgetViewZ. -
linear_depth(view_z)isviewZToOrthographicDepth: zero at the near plane and one at the far plane. -
depth_at(u, v)reads the nearest pixel, held at the edges, as a depth texture withNearestFilterreads it. -
normal_at(x, y)andnormals()return the view-space normal of a pixel.
The normals come from the frame's normal attachment. The composer draws its frame with one when an SSAO, SAO or SSR pass is on. See Multiple render targets. Pass RenderTarget.normals as the last argument of DepthView to use them. three.js draws the scene again with a MeshNormalMaterial to get them.
A pixel with no normal in the attachment takes one from the depth. So does every pixel of a frame without an attachment. The reconstruction is the one in three.js's own GTAOShader. On each axis, it takes the neighbor that continues the surface better, and crosses the two slopes. A neighbor past the edge of the frame reads zero, as WebGL's texelFetch reads it.
ssao_pass is three.js's SSAOPass. The pass draws the scene. Then, for each pixel that holds a surface, it tests a kernel of samples in the hemisphere above the surface.
-
ssao_kernel(size, seed)isgenerateSampleKernel. Each sample is a random direction above the surface. Theith ofnsamples has a length of 0.1 + 0.9 (i / n)². -
ssao_noise(seed)isgenerateRandomKernelRotations. It gives 16 values that repeat over the frame every four pixels. Each value turns the kernel about the normal. -
ssao_occlusionisSSAOShader. The kernel is scaled bykernel_radiusand set on the surface. A sample occludes when the frame's linear depth where it lands is in front of it. The difference must be more thanmin_distanceand less thanmax_distance. -
ssao_blurisSSAOBlurShader. It averages the five by five pixels around each pixel.
The blurred value multiplies the light of each pixel. Alpha is kept. kernel_radius is a Length. min_distance and max_distance are fractions of the camera's near-to-far range, as in three.js.
sao_pass is three.js's SAOPass. sao_occlusion is SAOShader. Seven samples spiral out from each pixel over four turns. A hash of the pixel and a seed turns the start of the spiral. The seed is three.js's randomSeed. The composer draws a new seed every frame from sao.seed, as three.js draws one from Math.random.
-
sao_sample_occlusionisgetOcclusion. It returns the slope toward the sample, lessbias, over the scaled distance. The result is divided by one plus that distance squared. - The occlusion is the average of the samples that land on a surface, scaled by
intensity. A pixel with no such sample keeps its light. - With
bluron,depth_limited_blurisDepthLimitedBlurShader. It blurs down, then across, overblur_radiuspixels withblur_std_dev.blur_weightsisBlurShaderUtils.createSampleWeights. On each side, the blur stops at the first neighbor whose distance differs by more thanblur_depth_cutoffof the range.
One minus the occlusion multiplies the light of each pixel. kernel_radius is in pixels, as in three.js.
ssr_pass is three.js's SSRPass. The pass draws the scene. ssr_reflections is SSRShader. From each surface, a ray leaves mirrored about the normal. The ray goes out max_distance from the surface's plane. It stops at the near plane.
The pass walks the ray across the screen one pixel at a time. The first surface that the ray passes behind is a hit. The surface must be within thickness of the ray, or within three pixels' width. With infinite_thick on, every surface behind the ray is a hit. A hit surface must face the ray. A hit farther than max_distance from the plane ends the walk with no reflection.
The strength of a reflection starts at opacity. With distance_attenuation on, it fades with the square of the distance. With fresnel on, it fades as the view meets the surface square on. With blur on, ssr_blur is SSRBlurShader, applied twice. The pass lays each reflection over the light by its strength, as three.js's NormalBlending does.
With selective on, only the objects on the selects layers reflect. three.js takes a list of objects and draws a metalness mask. This port draws the selected layers alone, as the outline pass does, and a pixel that they do not cover reflects nothing.
With bouncing on, the reflections are read from the frame that the pass made last time, as three.js reads its prevRenderTarget. So the reflections gather one more bounce each frame. The first frame reads transparent black, as three.js's fresh target holds.
ReflectorForSSR in objects/reflector_for_ssr.mojo is three.js's ReflectorForSSRPass: a mirror on the ground under the SSR pass. It is a Reflector with three.js's own shader for the pass.
var ground = ReflectorForSSR(assets, square, floor_node, use_depth_texture=True)
scene.add_mesh(ground.mesh.copy())
var step = ssr_pass()
step.ssr.ground = Layers(UInt32(1 << 3)) # the layers the mirror's node is on- Put the mirror's node on layers of its own, and give those layers to the pass as
ground. The pass lays no reflection over the mirror's pixels, because the mirror shows its own. three.js hides the mirror from the pass's normals instead, which leaves the same pixels unreflected. - Call
ground.update(renderer, scene, assets, camera)before each frame, as for aReflector. three.js's pass callsdoRenderin its own beauty render. The composer here reads the scene and does not change it, so the caller updates the mirror.
Without a depth texture, the mirror overlays the mirrored scene with its color, as a Reflector does. With use_depth_texture, it reads the height of each reflected point above the ground and blends. The reflection starts at opacity. It fades with the square of the height over max_distance (distance_attenuation), and by a fresnel factor of the camera's height (fresnel). A point higher than max_distance is not reflected.
The ground is the plane y = 0 in world space, and the clipping plane is y = -clip_bias, as in three.js. The fresnel factor reads the camera's world position. three.js reads its local position, which is the same for a camera at the scene's root.
outline_pass(selection) is three.js's OutlinePass. three.js takes a list of objects. This port takes a set of layers: the objects on those layers are selected. An empty set outlines nothing, as three.js's pass does with no objects.
- The pass draws the depth of the whole scene. Then it draws the depth of the selected layers alone, through the same camera.
-
outline_maskmarks the selected pixels. It also marks a selected pixel where something else is in front. three.js compares the selected objects with the depth of the other objects. The whole scene is nearer than a selected surface exactly where another object is in front, so the result is the same. - The mask is copied to half the frame's size. The edges are found there, and colored
visible_edge_colororhidden_edge_color. - The edges are blurred at half size over
edge_thickness, and at a quarter size over four texels. - The pass adds the narrow blur plus the wide blur times
edge_glow, scaled byedge_strength, to the light outside the selected objects.
With a pulse_period above zero, the edge colors pulse. three.js reads the clock. This port reads the time that delta_time adds up.
With use_pattern_texture on, the selected objects are filled with a pattern. The pattern is the texture that pattern_texture names, in the assets, read at six times the frame's texture coordinate. The fill adds one less the pattern's red, and half of that where the object is hidden, as three.js's overlay adds it.
ScreenSpaceOutput is a type. It is three.js's SSAOPass.OUTPUT, SAOPass.OUTPUT and SSRPass.OUTPUT in one. Every value applies to the SSAO, SAO and SSR passes.
| Output | three.js | Meaning |
|---|---|---|
DEFAULT_OUTPUT |
Default |
The effect over the frame. |
EFFECT_OUTPUT |
SSAO, SAO, SSR
|
The effect alone, before its blur. |
BLUR_OUTPUT |
Blur |
The effect alone, after its blur. |
BEAUTY_OUTPUT |
Beauty |
The frame as it came in. |
DEPTH_OUTPUT |
Depth |
One minus the linear depth, as gray. |
NORMAL_OUTPUT |
Normal |
The view-space normal, packed from zero to one. |
check_ssao, check_sao, check_ssr and check_outline refuse settings that no pass can use. check_pass calls all four, so add_pass and render call them too.
- Every check refuses a setting that is not finite, and an output that is none of the six.
-
check_ssaorefuses a negative radius or distance, and a kernel of no samples. -
check_saorefuses a negative intensity, minimum resolution, blur radius or cutoff. It refuses a scale, kernel radius or deviation that is not positive. -
check_ssrrefuses an opacity outside zero to one, a negative thickness, and a reach that is not positive. -
check_outlinerefuses a negative color channel, strength, glow or period, and a thickness that is not positive. -
DepthViewrefuses a size that is not positive and a depth of the wrong length. It refuses a list of normals of the wrong length. It refuses a near distance that is not before the far distance, and a projection with no inverse.
-
The normals. three.js draws a normal pass. This port reads the normals from the frame's normal attachment, filled in the same pass as the light. Where the attachment has none, it reconstructs them from the depth, as three.js's
GTAOShaderdoes. There a curved surface shows its triangles, because the depth of a triangle is flat. -
The randomness. three.js uses
Math.random. This port usesSeededRandom, so the same seed gives the same frame. The SSAO noise is a random value from minus one to one. three.js puts two random values through simplex noise first. - The divisions by zero. three.js divides zero by zero in three places. SSAO turns the kernel by a noise parallel to the normal. SAO reads a sample at the pixel's own point. SSR reflects from a surface seen edge on. This port gives no occlusion or no reflection there.
-
SSR with no neighbors. Where no neighbor of a pixel reflects anything,
ssr_blurgives black. three.js divides by zero there. -
The outline's alpha. three.js adds the outline with
AdditiveBlending, which also adds to alpha. This port keeps alpha, as the bloom does. -
The effect output of SAO. three.js's
OUTPUT.SAOshows the occlusion after its blur. This port shows it before the blur, andBLUR_OUTPUTshows it after.
- The outline's
downSampleRatio, which is fixed at two as in three.js. - A pass that the renderer's viewport or scissor narrows. The passes take the camera to fill the whole frame.
GTAOPass is ported. See GTAO.
postprocessing/effects.mojo. Eight more passes blur by depth, glitch, draw a halftone, mask, clear, add a texture and grade through a lookup table. Each is a builder in postprocessing/composer.mojo, and each has three.js's defaults.
var composer = EffectComposer()
composer.add_pass(clear_pass())
composer.add_pass(mask_pass(Layers(UInt32(2))))
composer.add_pass(texture_pass(texture))
composer.add_pass(clear_mask_pass())
var image = composer.render(renderer, scene, assets, camera)That is three.js's masking example. The texture fills only the pixels that the objects on layer one cover.
bokeh_pass is three.js's BokehPass. Put it after a render_pass. It draws the scene's depth itself and blurs the frame that it is given.
-
bokeh_bluris the shader'sdofblur. The focus plus the view-space z, timesaperture, is the reach. The reach is clamped tomax_blurin each direction. -
bokeh_lightisBokehShader. Each pixel is the average of 41 taps. They are the pixel, 16 taps at the full reach, and 8 taps each at 0.9, 0.7 and 0.4 of it.bokeh_tapsreturns their offsets. - The vertical offsets are scaled by the frame's aspect. The result is opaque, as the shader sets alpha to one.
focus is a Length. aperture is in texture widths per meter. max_blur is in texture widths.
bokeh2_pass is a ShaderPass of three.js's BokehShader2. Put it after a render_pass. It draws the scene's depth itself and blurs the frame that it is given. Bokeh2Settings holds the shader's uniforms with three.js's defaults, and its RINGS and SAMPLES defines, three and four.
- The blur comes from the lens: the focal length, the f-stop and the distance to the focal plane. With
manual_dof, it comes from the fixed near and far ranges of the shader instead. - With
shader_focus, the focal plane is the depth atfocus_u,focus_v. Otherwise it isfocal_depth, in meters. - Each ring of taps holds
samplestimes its number of taps. A tap is fringed byfringe, and a bright tap is raised bythresholdandgain. -
pentagon,depth_blur,show_focusandvignettingare the shader's own switches.
texture_width and texture_height of zero read the frame's size. three.js starts them at one and has the caller set them. The shader's bdepth spells one offset vec2( wh.x -wh.y ), and the port keeps that.
dof_mipmap_pass is a ShaderPass of three.js's DOFMipMapShader. Put it after a render_pass. It draws the scene's depth itself. Each pixel reads the frame at a mip level of 2 * max_blur * abs(focus - depth), where the depth is the window depth, zero to one. The mips halve the frame to one pixel, a two by two mean each, and a level between two mixes them, as LINEAR_MIPMAP_LINEAR does.
classic_bloom_pass is three.js's BloomPass. bloom_pass is UnrealBloomPass. The frame is blurred across and then up by ConvolutionShader, with kernel_size taps 1/512 of the texture apart. The kernel is buildKernel( sigma ), at most 25 taps, and the uniforms past it are zero. The blur, times strength, is added with three.js's additive blend, so the added light is the blur times the strength twice.
These three passes run on the host in a GpuComposer frame, as the other passes that draw depth do.
glitch_pass(size, seed) is three.js's GlitchPass. Each frame that render draws advances it. The draws come from SeededRandom, in three.js's order.
-
GlitchSettings(size, seed)is the constructor. It draws the displacement map, then the first trigger from 120 to 240. -
glitch_uniformsis the start ofrender. A frame whose count is a multiple of the trigger is wild: a large shift, a wide tear and a new trigger. Withgo_wildon, every frame is wild. - The frames in the first fifth after a wild frame glitch a little. The other frames are not changed.
-
glitch_lightisDigitalGlitch. It tears a row band and a column band, and pushes the frame by the map. It moves red and blue apart and adds snow.
glitch_heightmap(size, seed) returns the displacement map. It is read with the nearest texel, held at the edges.
halftone_pass(radius) is three.js's HalftonePass. Its other settings are the fields of halftone, with three.js's names and defaults.
- Each channel has a grid at its own angle:
rotate_r,rotate_gandrotate_b. The grid points areradiuspixels apart. -
reference_cellfinds the four grid points around a pixel.scattermoves each point by a random amount, up to a quarter of the spacing. -
halftone_sampleaverages nine taps around a grid point. That average sizes the dot at the point. -
dot_radius_distancemeasures a pixel against a dot. The shape isHALFTONE_DOT,HALFTONE_ELLIPSE,HALFTONE_LINEorHALFTONE_SQUARE. -
halftone_blendmixes the halftone with the frame byblending. The mode isHALFTONE_LINEAR,HALFTONE_MULTIPLY,HALFTONE_ADD,HALFTONE_LIGHTERorHALFTONE_DARKER.
With grayscale on, the three channels are averaged. With disable on, the frame is not changed. The result is opaque.
halftone.set_size(width, height) is three.js's HalftonePass.setSize. It lays the grid over that size in pixels. A size of zero, the default, is the frame's own size, as the composer's setSize gives it.
mask_pass(selection, inverse) is three.js's MaskPass. clear_mask_pass() is ClearMaskPass. Between the two, a pass changes only the pixels inside the mask.
- The mask pass draws the objects on the selected layers alone, through the same camera.
-
mask_stencilwrites the render target's stencil buffer. It writes one where an object covers the pixel, and zero at the other pixels. Withinverseon, it writes zero and one. - After each pass,
keep_outside_maskputs back each pixel whose stencil is not one.inside_maskis the test that three.js's mask leaves on: equal to one. - The clear mask pass stops the test. A disabled mask pass or clear mask pass does nothing, as in three.js.
The mask pass does not change the light or the depth, as three.js turns off those writes.
three.js's mask pass takes a scene of its own. This port draws the selected layers of the composer's scene, whatever layers the camera shows. So put the mask's objects on a layer that the camera does not show, and they draw in the mask alone.
A mask pass has clear on, as three.js's has. With clear off, the stencil is not cleared first. A pixel that the objects do not cover keeps its stencil, so the mask adds to the one before it. A texture pass has clear off. With clear on, it clears the frame to the renderer's background first.
clear_pass(color) is three.js's ClearPass. clear_light sets the light to the color, the depth to far, and the stencil to zero. The color is in sRGB, and its alpha is three.js's clearAlpha.
texture_pass(texture, opacity) is three.js's TexturePass. The texture is a TextureId in the assets that render is given. texture_light scales the texture by opacity and draws it over the frame, alpha included. Below an opacity of one, the frame keeps one minus the scaled alpha of its color. At an opacity of one, the texture replaces the frame. That is CopyShader with three.js's premultiplied normal blending, which is on only below an opacity of one.
lut_pass(lut, intensity) is three.js's LUTPass. It grades the frame through a color lookup table, a 3D texture in the assets. read_lut_cube in loaders/lut_cube.mojo reads the table from a .cube file, as three.js's LUTCubeLoader does.
var table = read_lut_cube("grade.cube")
var lut = assets.data_3d_textures.add(table.texture^)
composer.add_pass(render_pass())
composer.add_pass(output_pass())
composer.add_pass(lut_pass(lut, intensity=1.0))Put the LUT pass after the output pass, as three.js's example does. Then the table reads the colors that a display shows.
-
lut_lightencodes the straight color of each pixel through the sRGB curve. three.js's shader reads the target thatOutputPassencoded. -
lut_colorisLUTShader. It pulls the color in by half a texel, so that zero and one land on the centers of the edge texels. It reads the table at that coordinate: red across, green up and blue deep. The table's size is its width, aslutSizeis. - The looked-up color replaces the color by
intensity, as the shader'smixdoes. Alpha is kept. -
lut_lightdecodes the result. The encode at the end of the frame gives the numbers that three.js's shader wrote.
parse_lut_cube(text, texel_type=UNSIGNED_BYTE_TYPE) reads the text of a .cube file. It returns a LutCube with title, size, domain_min, domain_max and texture. These are three.js's title, size, domainMin, domainMax and texture3D.
-
TITLE "name"sets the title.LUT_3D_SIZE nsets the size, two through 256. -
DOMAIN_MINandDOMAIN_MAXare kept. They are not applied, because three.js'sLUTPassdoes not apply them. - Each line of three numbers is one entry. Red changes fastest, then green, then blue. There must be
sizecubed entries. - A line that starts with
#is a comment. Another keyword, such asLUT_3D_INPUT_RANGE, is skipped, as three.js skips it. - With
UNSIGNED_BYTE_TYPE, each number times 255 is cut to a whole byte, as aUint8Arraycuts it. WithFLOAT_TYPE, the numbers are kept as they are. - The texture is clamped on every axis,
BILINEARandLINEAR, as three.js sets it.
check_bokeh, check_glitch and check_halftone refuse settings that no pass can use. check_pass calls all three.
-
check_bokehrefuses a focus, aperture or largest blur that is negative or not finite. -
check_glitchrefuses a map size outside one through 4096, and a generator state that does not fit 32 bits. It refuses a negative frame count and a trigger that is not positive. -
check_halftonerefuses a shape or a blending mode that is not named. It refuses a setting that is not finite, and a radius that is not positive. It refuses a negative scatter and a blending outside zero to one. -
check_passrefuses a texture pass withNO_TEXTURE.renderrefuses a texture id that is not in the assets. -
check_passrefuses a LUT pass withNO_DATA_3D_TEXTURE, and a negative intensity.renderrefuses a table that is not in the assets.lut_lightcalls the table'svalidate. -
parse_lut_cuberefuses a texel type that is not named, a file with noLUT_3D_SIZE, and aLUT_1D_SIZE. It refuses a size outside two through 256. -
parse_lut_cuberefuses a line that does not hold three finite numbers, and a count of entries that is notsizecubed. It refuses a domain minimum above its maximum. - For bytes,
parse_lut_cuberefuses a number outside zero to one. AUint8Arraywraps such a number, and three.js does not check it.
HalftoneShape and HalftoneBlending are types. A bare number does not compile. tests/compile_fail/ proves it.
-
The mask selection. three.js's
MaskPasstakes a scene and a camera. This port takes a set of layers, as the outline pass does, and the composer's camera. - The mask coverage. A pixel is in the mask where a selected object writes its depth. A surface that writes no depth does not add to the mask.
- Every pass obeys the mask. In three.js, a clear ignores the stencil test. So a clear pass, and a render pass that clears, clear the whole frame inside a mask. This port keeps the pixels outside the mask for every pass.
-
The bokeh depth. three.js draws the depth again with a
MeshDepthMaterial. This port draws the scene as a render pass does, so a surface withdepth_writeoff is not in the depth. - The bokeh aspect. three.js reads the camera's aspect. This port reads the frame's width over its height.
-
The randomness. three.js uses
Math.random. This port usesSeededRandom, so the same seed gives the same frames. - The texel. The bokeh, glitch and halftone read the light as stored, premultiplied. For an opaque pixel that is the straight color.
- The LUT's input. three.js's LUT pass reads whatever the target holds. This frame holds linear light, so the LUT pass encodes it to sRGB first. Before the output pass, three.js's table reads linear light, and this port's table reads encoded color.
-
The
.cubereader is stricter. three.js reads each line that matches its pattern and ignores the others. This port refuses a malformed line and a wrong count of entries.
- The LUT pass's 2D table, which three.js once used for WebGL 1.
LUT3dlLoader and LUTImageLoader are ported. They read .3dl files and image strips into a 3D texture for lut_pass. See More model files and LUT image.
postprocessing/render_passes.mojo, postprocessing/gtao.mojo and postprocessing/shader_pass.mojo. Six more passes draw the scene anew, darken it by occlusion, keep a copy of the frame and run a program over it.
var composer = EffectComposer()
composer.add_pass(render_pixelated_pass(pixel_size=4))
composer.add_pass(gtao_pass())
composer.add_pass(output_pass())
var image = composer.render(renderer, scene, assets, camera)render_pixelated_pass is three.js's RenderPixelatedPass. Put it where a render_pass goes. It draws the scene into a target a pixel size smaller each way, with its depth and its normals. sized_renderer makes the small renderer with the same settings.
pixelated_light shows each drawn texel as a block of pixels, with the nearest texel. pixelated_strength is the pass's shader. Where a neighbor is behind the texel, the depth edge darkens it by depth_edge_strength. Otherwise, where a neighbor faces another way, the normal edge lightens it by normal_edge_strength.
gtao_pass is three.js's GTAOPass. Put it after a render_pass. It draws the depth and the normals itself. The fields of GtaoSettings are three.js's parameters, with its defaults.
-
gtao_occlusionisGTAOShader. It cuts the hemisphere above each surface into three slices, or five for thirty samples or more. In each slice it finds the highest horizon both ways, and it integrates the visible arc against the normal. -
gtao_noiseturns the slices from pixel to pixel by a five by five magic square, asgenerateMagicSquareNoisedoes. -
gtao_denoiseisPoissonDenoiseShader. It averages sixteen taps on a Poisson disk, each weighed by how like the pixel it is. - With the default output, the frame is multiplied by the result, mixed toward one by
blend_intensity. The other five outputs are the SSAO pass's. See Outputs.
render_transition_pass(first, second, texture, mix_ratio) is three.js's RenderTransitionPass. Put it where a render_pass goes. It draws the objects on the first layers and then on the second layers, through the composer's camera. transition_pixel mixes the two views. A mix_ratio of one shows the first view, and zero shows the second.
With a texture, a threshold sweeps across the texture's red channel as the ratio moves. threshold sets how soft the edge is. Without one, the two views are mixed evenly.
render_transition(renderer, first_scene, first_camera, second_scene, second_camera, assets, settings, texture) draws two scenes, each through its own camera, as three.js's pass does. It returns the image.
cube_texture_pass(cube, opacity) is three.js's CubeTexturePass. cube_overlay reads the cube in the direction of each pixel's ray. The camera's position does not change it. cube_texture_light draws it over the frame as the texture pass draws.
save_pass() is three.js's SavePass. It keeps a copy of the frame and changes nothing. EffectComposer.saved_image returns the copy.
shader_pass(program, input) is three.js's ShaderPass. The program is a node program in the assets' programs. Compile a fragment shader against SCREEN_VERTEX_SHADER with compile_shader_material, or build a NodeGraph.
var program = compile_shader_material(SCREEN_VERTEX_SHADER, fragment)
program.set_uniform("amount", Float32(0.5))
var id = assets.programs.add(program^)
var step = shader_pass(id)
step.shader.saved = "tSaved"
step.shader.saved_pass = 1
composer.add_pass(step^)- The sampler named
input,tDiffuseby default, reads the frame before the pass. - The sampler named
shader.savedreads the image of the save pass atshader.saved_pass. Before that pass runs, it reads transparent black. - Every other sampler reads a texture in the assets.
-
uvand a varying copied from it are the pixel's texture coordinate. Thetimenode reads the pass'stime, whichdelta_timeadvances. - The output node, or else the color node, is the straight color. The opacity node is the alpha. A fragment that the mask node throws away keeps the pixel.
-
check_pixelatedrefuses a pixel size below one, and an edge strength that is negative or not finite. -
check_gtaorefuses an output that is not named and a setting that is not finite. It refuses a radius, thickness, scale or phi that is not positive, and a fall-off outside zero to one. It refuses a negative intensity or exponent, fewer than two samples, and a denoise radius below one pixel. -
check_transitionrefuses a setting that is not finite, and a threshold that is not positive. -
check_shaderrefusesNO_NODES, and a saved sampler that names no pass. -
check_passrefuses a cube texture pass withNO_CUBE_TEXTURE, and a transition pass that reads a texture withNO_TEXTURE. -
shader_lightrefuses a saved image that is not the frame's size, and a texture that is not in the store.saved_imagerefuses an index that names no save pass.
-
The transition's scenes. three.js's pass takes two scenes and two cameras. The pass takes two sets of layers and the composer's camera.
render_transitiontakes two scenes and two cameras. -
The shader's program. three.js compiles GLSL for WebGL. The pass runs a node program. GLSL runs through the subset that
compile_shader_materialknows. -
The GTAO noise. three.js seeds the denoise's simplex noise from
Math.random. This port seeds it fromseed.
postprocessing/shaders.mojo. Eighteen of three.js's screen shaders, each run as a ShaderPass runs it, and its god-rays chain.
composer.add_pass(render_pass())
composer.add_pass(hue_saturation_pass(hue=0.2, saturation=0.3))
composer.add_pass(god_rays_pass(Vector3(0, 1000, -1000)))effect_pass(effect) runs the shader that a ShaderEffect names, with three.js's default uniforms. Change the uniforms in the fields of effect, an EffectSettings.
| Effect | Builder | three.js |
|---|---|---|
RGB_SHIFT |
rgb_shift_pass(amount=0.005, angle=0) |
RGBShiftShader |
BRIGHTNESS_CONTRAST |
brightness_contrast_pass(brightness=0, contrast=0) |
BrightnessContrastShader |
HUE_SATURATION |
hue_saturation_pass(hue=0, saturation=0) |
HueSaturationShader |
COLOR_CORRECTION |
color_correction_pass(pow_rgb, mul_rgb, add_rgb) |
ColorCorrectionShader |
COLORIFY |
colorify_pass(color=white) |
ColorifyShader |
BLEACH_BYPASS |
bleach_bypass_pass(opacity=1) |
BleachBypassShader |
TECHNICOLOR |
technicolor_pass() |
TechnicolorShader |
SOBEL |
sobel_pass() |
SobelOperatorShader |
FREI_CHEN |
frei_chen_pass() |
FreiChenShader |
KALEIDO |
kaleido_pass(sides=6, angle=0) |
KaleidoShader |
MIRROR |
mirror_pass(side=MIRROR_RIGHT) |
MirrorShader |
HORIZONTAL_TILT_SHIFT and VERTICAL_TILT_SHIFT
|
tilt_shift_pass(across=True, spread=1/512, focus=0.35) |
HorizontalTiltShiftShader and VerticalTiltShiftShader
|
EXPOSURE |
exposure_pass(exposure=1) |
ExposureShader |
GAMMA_CORRECTION |
gamma_correction_pass() |
GammaCorrectionShader |
TRIANGLE_BLUR |
triangle_blur_pass(delta_u, delta_v) |
TriangleBlurShader, reading the frame as its texture
|
NORMAL_MAP |
normal_map_pass(height, resolution_x, resolution_y) |
NormalMapShader, reading the frame's red as its heightMap
|
FOCUS |
focus_pass(sample_distance, wave_factor, screen_width, screen_height) |
FocusShader |
effect_pixel is one pixel of any of the eighteen, line for line with its fragment shader. A color transform reads the straight color of the pixel and premultiplies the result. Some effects read their neighbors, bilinear and clamped at the edges. They are the shift, the edge filters, the kaleidoscope, the mirror, the tilt shifts, the triangle blur, the normal map and the focus.
god_rays_pass(sun, intensity, fake_sun) runs three.js's god-rays example after the scene. The fields of GodRaysSettings are the example's values, with its defaults.
-
god_rays_maskisGodRaysDepthMaskShaderon the scene's depth. -
god_rays_generate_pixelisGodRaysGenerateShader. Three passes walk the mask toward the sun, atresolution_scaleof the frame's size. Each step is a sixth of the step before. -
god_rays_combine_pixelisGodRaysCombineShader. It adds the rays to the frame. Withfake_sunon,fake_sun_colorisGodRaysFakeSunShader, a glow where nothing was drawn.
sun_on_screen projects the sun through the camera, as the example does.
-
check_effectrefuses an effect or a mirror side that is not named, and a uniform that is not finite. It refuses a contrast of one or more, where the shader divides by zero. It refuses a saturation above one and sides that are not positive. -
check_god_raysrefuses a setting that is not finite, a negative intensity and a filter length that is not positive. It refuses a resolution scale outside above zero to one.
ShaderEffect and MirrorSide are types. A bare number does not compile. tests/compile_fail/ proves it.
postprocessing/display_nodes.mojo. Seven of three.js's TSL display nodes run as composer passes, with three.js's defaults.
| Builder | three.js | What it does |
|---|---|---|
gaussian_blur_pass(sigma=4, resolution_scale=1) |
gaussianBlur |
Blur across and then down, at a resolution scale. |
box_blur_pass(size=1, separation=1) |
boxBlur |
Average a square of taps. |
hash_blur_pass(blur_amount=0.1, repeats=45) |
hashBlur |
Average taps round a circle, at hashed distances. |
chromatic_aberration_pass(strength=1, center_u=0.5, center_v=0.5, scale=1.1) |
chromaticAberration |
Read red further out and blue further in. |
anamorphic_pass(threshold=0.9, scale=3, samples=32) |
anamorphic |
Add a blue streak of the bright light along each row. |
lensflare_pass(threshold=0.5, ghost_samples=4) |
lensflare |
Add ghosts of the bright light, mirrored through the center. |
dof_pass(focus_distance=1 m, focal_length=1 m, bokeh_scale=1) |
dof |
Blur the near field and the far field apart, with a bokeh. |
denoise_pass(luma_phi=5, depth_phi=5, normal_phi=5, radius=5) |
denoise |
Average each pixel with the taps around it that are like it. |
ssr_node_pass(max_distance=1 m, thickness=0.1 m, intensity=1, quality=0.5) |
ssr |
Add what each metallic surface reflects. See SSR node. |
motion_blur_pass(amount=1, samples=16) |
motionBlur |
Blur each pixel along its velocity. |
Each pass keeps its settings in display, a DisplaySettings, with three.js's names.
- A blur and the chromatic aberration replace the frame, as a node set as the output does.
- The anamorphic streak and the lens flare are added to the frame, as three.js's examples add them to the scene pass.
- A node that draws into a smaller target is worked out at that size and read back bilinear.
- The three blurs take
premultiplied_alpha, three.js'spremultipliedAlpha. Off, they blur straight color, as three.js's texture holds it. The alpha is blurred too, so the stored light is scaled by it.
GaussianBlurNode's weights are not normalized, as three.js leaves them. So a blur of an even frame is a little darker than the frame. hashBlur turns each tap's angle into degrees and takes the cosine of that, as the node does.
dof_pass is three.js r186's DepthOfFieldNode, in its five stages. Each pixel's circle of confusion is smoothstep(0, focal_length, abs(distance - focus_distance)), from its view distance. The circle is kept apart for the near field and the far field.
The near field's circle is blurred by gaussianBlur(coc, 1, 2) and halved in size. Then each field blurs the frame at half size with 64 taps on a golden-angle spiral, spread by its circle. It spreads the brightest of 16 more taps of that. The composite mixes the far blur over the frame and the near blur over that, each by twice its circle, at most one.
denoise_pass is three.js r186's DenoiseNode over the frame. The composer draws its frame with a normal attachment while the pass is on. Each pixel that holds a surface averages 16 taps on a disk, which a noise texture turns. Each tap weighs by how like the pixel it is in luminance, in depth along the normal, and in normal.
The node's texture coordinate grows down the image, so its disk is upside down against the WebGL PoissonDenoiseShader. The node reads the noise as the angle itself, where the shader multiplies it by two pi. This port does both as the node does. The noise is seeded, as the GTAO pass's is.
The DoF and denoise passes, like the other display passes, run on the host in a GpuComposer frame.
ssr_node_pass is three.js r186's SSRNode in its default mode, in postprocessing/ssr_node.mojo. It adds the reflections to the frame, as three.js's SSR example adds them. The composer draws its frame with a normal attachment and a metalness attachment while the pass is on.
Each pixel with a surface and some metalness sends a ray along its reflection. The ray is max_distance long along the view and stops at the near plane. The march crosses the image one pixel at a time, times quality. The ray hits where it passes behind the depth, within thickness of the surface there. A surface that faces away from the ray is passed over. A hit farther from the pixel's plane than max_distance ends the march.
A hit reads the frame's color there. The color is scaled by the metalness, by the square of the part of max_distance that is left, and by three.js's Fresnel term, (dot(incident, reflected) + 1) / 2. The light is capped at max_luminance and scaled by intensity.
With use_roughness on, the reflections go into a chain of five levels. Each level after the first is a box blur, a level's number of pixels apart. The frame reads the chain at the level that its roughness squared, times four, gives. This is how the example reads the node through its roughnessNode.
The node's stochastic mode, its environment sampling, its history and its binary refinement are not ported. The example leaves all four off. The pass runs on the host in a GpuComposer frame.
bayer16(x, y) is three.js's bayer16: the 16 by 16 Bayer matrix, zero to one, from the node's own image.
The motion blur reads the frame's velocity attachment. The composer draws its frame with one while a motion blur pass is on. The pass scales each velocity by amount, three.js's example's blurAmount.
Then it sums the taps as three.js's node sums them. The first tap is the pixel. Each other tap is i / (samples - 1) - 0.5 of the velocity along, for i from one to samples. The sum is divided by samples, one less than the taps, as three.js divides it. The node adds the velocity to a texture coordinate that grows down the image, so the vertical taps go against the velocity, as three.js's do.
The camera and the objects move between two calls to render. So the first frame is sharp.
The motion blur runs on the device in a GpuComposer frame, with the velocities of the host frame. motion_blur_pixel does the work on both sides. The other display passes run on the host in a GpuComposer frame.
Twelve more of three.js r186's TSL display nodes run as composer passes, with three.js's defaults. They are in postprocessing/upscaling.mojo, taau.mojo, filter_nodes.mojo, ssgi.mojo, temporal_denoise.mojo, oit.mojo, retro.mojo and importance_sampled_environment.mojo.
| Builder | three.js | What it does |
|---|---|---|
sharpen_pass(sharpness=0.2, denoise=False) |
sharpen |
Sharpen the frame by robust contrast-adaptive sharpening. |
fsr1_pass(sharpness=0.2, denoise=False, resolution_scale=1) |
fsr1 |
Upsample the frame by EASU, then sharpen it by RCAS. |
taau_pass(resolution_scale=0.5) |
TAAUNode |
Draw the scene jittered at a smaller size, and resolve it into a history at the frame's size. |
crt_pass(curvature=0.1, bleeding=0.002, scanline_intensity=0.3) |
CRT.js |
Bend, smear, line and darken the frame as a cathode-ray tube shows it. |
radial_blur_pass(center_u=0.5, center_v=0.5, count=32, exposure=5) |
radialBlur |
Blur the frame toward a center. |
bilateral_blur_pass(direction=1, sigma=4, sigma_color=0.1) |
bilateralBlur |
Blur the frame across and down, and keep its edges. |
depth_aware_blur_pass(sharpness=2, radius=1 m) |
depthAwareBlur |
Blur the frame across and down, and stop at steps in the depth. |
depth_aware_blend_pass(texture, blend_color=white) |
depthAwareBlend |
Mix a color over the frame by a texture's red, pushed off the depth's edges. |
ssgi_pass(slice_count=1, step_count=12, gi_intensity=10, denoise=False) |
ssgi |
Add the light that surfaces bounce onto each other, and darken the creases. |
sss_pass(light=LightIndex(0), max_distance=0.1 m, thickness=0.01 m) |
sss |
Shadow each pixel that the depth hides from the main light. |
oit_pass() |
oitPass |
Draw the scene, and weigh its transparent objects in any order. |
retro_pass(resolution_scale=0.25) |
retroPass |
Draw the scene small, with its corners snapped to its pixels. |
The passes keep their settings in upscale, taau, filters, ssgi, temporal_denoise, sss and retro. Their kinds start at 70, so 60 stays a kind that no pass has. Put taau_pass, oit_pass and retro_pass where a render_pass goes. They draw the scene themselves.
Each node reads the texture coordinate of three.js's WebGPU uv(), which grows down the image. The functions take v in that sense, so the arithmetic is three.js's as written.
sharpen_pass is three.js's SharpenNode, RCAS from AMD FidelityFX. Each pixel reads itself and its four neighbors in a cross. The darkest and the brightest channel of the ring limit a negative lobe, which is scaled by 2^-sharpness. With denoise on, the lobe is weighed down where the ring's luminance is noisy.
fsr1_pass is three.js's FSR1Node. The pass reads the frame at resolution_scale of its size, as a scene pass with that scale gives it. EASU finds the direction of the edge from twelve input texels. It weighs them by a Lanczos kernel stretched along the edge, and clamps the result to the four nearest texels. Then RCAS sharpens the result.
Two things differ from three.js. A texel past the edge of the image is the edge texel, as WebGPU's robust textureLoad gives it. RCAS divides zero by zero on a black ring. max_num and min_num then take the operand that is a number, as a GPU's max does.
taau_pass is three.js's TAAUNode, with the scene pass that it reads. Each frame, the pass draws the scene at resolution_scale of the frame's size. The camera moves by the next of 32 Halton offsets, less a half, in input pixels. The pass draws with Renderer.resized, which shares the velocity history of the composer's renderer.
Each output pixel finds the nearest jittered input sample. The 3 by 3 taps round it give the depths and the velocity, as TRAA reads them. The same nine taps rebuild the color, each weighed by exp(-2.29 d^2) of its distance. They also give the box that the history is clipped to.
A thin feature locks the history against the clip where the depth did not change. three.js writes the lock to a target with one attachment. So the lock history stays at the seed's zero, and this port keeps that. The history is seeded with the input read bilinear at the output's pixels.
crt_pass runs the five functions of three.js's CRT.js in the order of three.js's documentation:
-
barrel_uvbends the texture coordinate outward from the center, and keeps the corners in place. -
color_bleedingreads the color there and smears the three taps to its left into it. Red goes furthest and blue least. -
scanlinesdarkens the lines. The composer moves the clock on by each frame's time, forscanline_speed. -
crt_vignettedarkens the edge throughcircle. -
barrel_maskmakes the pixels that bend off the screen black.
radial_blur_pass is three.js's radialBlur. Each pixel walks count taps toward the center. Each tap weighs weight times decay to the power of its number. The walk starts a share of a step along, from interleaved_gradient_noise. The sum over count, times exposure, is mixed with twice the pixel. An alpha above one is shown as one.
bilateral_blur_pass is three.js's BilateralBlurNode: the Gaussian taps of gaussian_coefficients, across and then down. Each tap is weighed down by how unlike the center its luminance is, by sigma_color.
depth_aware_blur_pass is three.js's depthAwareBlur: five Gaussian taps along a step. Each tap is weighed down by its distance in depth from the center, relative to radius. three.js filters the red channel alone. The pass runs it across and then down over red, green and blue alike.
depth_aware_blend_pass is three.js's depthAwareBlend. It reads its blend texture from the assets, as a texture pass does. Eight Poisson taps find the neighbors that share the pixel's depth. Their mean offset pushes the read of the blend texture. three.js calls normalize on the mean and does not keep the result. So the push is the mean offset, and this port keeps that.
ssgi_pass is three.js's SSGINode. The composer draws its frame with a normal attachment while the pass is on. Each pixel with a surface cuts the hemisphere over it into slice_count slices. It walks step_count taps each way along each slice.
The angles to each tap's front and back cover some of 32 sectors. The sectors that a tap covers first occlude the pixel and send it the tap's light. The AO is the share of the sectors covered, to the power of ao_intensity. The light is scaled by gi_intensity and held at a luminance of seven. The temporal filter turns the slices and offsets the taps each frame, as three.js's frameId does.
three.js's example mixes the AO and the light with the scene and its diffuse color. The frame has no diffuse color attachment, so the pass takes the frame's own color for it: frame * ao + frame * gi. The AO is kept in eight bits, as three.js's RedFormat target keeps it.
ssgi_pass(denoise=True) runs the raw signal through three.js's TemporalReprojectNode and then its RecurrentDenoiseNode, in their diffuse mode. The composer draws its frame with a velocity attachment too. The denoised result is the next frame's history, as the example's setHistoryTexture makes it. The AO stays the raw signal's.
- The reprojection reads the history where the velocity says that the pixel was, from four texels. Each texel weighs by how well its surface matches the pixel's plane and normal.
- It clips the history toward the neighbors' box in YCoCg. The confidence falls with the clip, with motion and where the reprojection stretches the image. Its alpha is one over the frames that it holds.
- The denoise takes eight taps on a Vogel disk in the plane of the surface. An analytic noise turns the disk. Each tap weighs by its luminance, its AO, its distance from the plane and its normal.
- Karis's weights mix the denoised history and the denoised raw signal.
The derivatives of the history's coordinate are the differences across each 2 by 2 quad, as a GPU's fine derivatives are. A normalized zero vector is zero here, where a GPU gives a NaN. AlphaSource says what the raw signal's alpha holds: AO_ALPHA for the SSGI, RAY_LENGTH_ALPHA or NO_ALPHA.
sss_pass is three.js's SSSNode. light is the main light, one of the scene's lights. Each pixel with a surface walks a ray toward the light, max_distance long. The walk crosses the image a pixel apart, times quality. A step where the ray is behind the depth by less than thickness shadows the pixel by shadow_intensity. The pass multiplies the frame's light by the result.
oit_pass is three.js's OITPassNode. A renderer's draw_filter draws the scene without its order-independent objects first. An object is order-independent when its material is transparent, has normal blending and has no transmission. Then the pass weighs each transparent fragment in front of that frame's depth, in any order:
- The accumulation adds
(rgb * a, a)timesoit_weight, three.js's default weight of the alpha and the view distance. - The revealage multiplies
1 - a. - The composite is
mix(accum.rgb / accum.a, frame.rgb, revealage), with the frame's alpha.
three.js blends every fragment of an object into its targets. This port draws each order-independent draw alone, with ONE_OIT_DRAW. It weighs what the draw leaves at each pixel, at its nearest depth. For an object that does not cover itself, such as a pane, that is each fragment.
retro_pass is three.js's RetroPassNode. It draws the scene at a quarter of the frame's size, with the renderer's snap_vertices on. Each corner is then rounded to a whole pixel from the center of the screen, as three.js's vertex node rounds it. The small image is kept in eight bits and shown with nearest filtering.
EquirectEnvironment is three.js's ImportanceSampledEnvironment and its EnvMapCDFGenerator. It keeps an equirectangular map in half floats, flipped as flipY asks. With importance sampling on, it builds three.js's tables of the rows' and the texels' distributions by luminance. sample_reflect, sample_environment_brdf and sample_environment_mis are three.js's three samples, with the GGX helpers of SpecularHelpers.js.
three.js gives the drawn texture coordinate to equirectUV, which takes a direction. This port turns the coordinate into its direction with equirect_uv_to_dir, which is what the code means. No pass reads the environment, because the SSR node's environment mode is not ported.
-
check_upscalerefuses a negative sharpness and a resolution scale outside zero to one. -
check_taaurefuses a negative threshold, a velocity length that is not positive, and a frame weight or a resolution scale outside zero to one. -
check_filtersrefuses a number that is not finite, a curvature of a half or more, and a radial blur of no taps. It also refuses sigmas and a radius that are not positive, and a negative edge radius. A depth-aware blend pass must name a texture. -
check_ssgiandcheck_sssrefuse counts below one, numbers that are not finite and a negative light index.sss_light_directionrefuses a light that the scene does not have. -
check_temporal_denoiserefuses anAlphaSourcethat is none of the three.check_draw_filterrefuses aDrawFilterthat is none of the three.tests/compile_fail/proves that a bare number is not either type.
The sharpen, CRT and radial blur passes run on the device in a GpuComposer frame. rcas_pixel, crt_pixel and radial_blur_pixel do the work on both sides. The other passes draw the scene or read the depth, so they run on the host in a GpuComposer frame.
-
The specular denoiser.
TemporalReprojectNode's specular mode reprojects the hit point along a ray length in alpha.RecurrentDenoiseNode's specular mode reads the roughness. The SSR node's stochastic mode, which makes that signal, is not ported. - The retro materials. three.js swaps each material for a Phong one unless it is basic. It mixes a standard material's environment into its color, and can map textures without the perspective divide. This port draws each material as it is.
- The packed targets. three.js packs the SSGI's light into 11, 11 and 10 bits and the OIT's revealage into eight. This port keeps both as floats.
- The diffuse color. The SSGI's example reads a diffuse color attachment. The frame's own color stands in for it.
-
The environment's pass. No pass reads
EquirectEnvironment.
renderers/stereo_effects.mojo, renderers/ascii_effect.mojo and renderers/outline_effect.mojo. Five of three.js's examples/jsm/effects/. Each wraps a renderer and draws the scene in its own way.
| Effect | three.js | Meaning |
|---|---|---|
StereoEffect() |
StereoEffect |
Draw the left eye in the left half and the right eye in the right half. |
AnaglyphEffect() |
AnaglyphEffect |
Mix the two eyes through two Dubois matrices, for red and cyan glasses. |
ParallaxBarrierEffect() |
ParallaxBarrierEffect |
Take alternate rows from each eye. |
AsciiEffect(characters, resolution=0.15, color, alpha, block, invert) |
AsciiEffect |
Draw the scene as characters. |
OutlineEffect(thickness=0.003, color=black, alpha=1) |
OutlineEffect |
Draw an ink outline around every mesh. |
var effect = OutlineEffect()
var image = effect.render(renderer, scene, assets, camera)
var ascii = AsciiEffect().render(renderer, scene, assets, camera)
print(ascii.text())Each stereo effect makes its two eyes from the camera with a StereoCamera. StereoEffect draws through an ArrayCamera, each eye at half the aspect. set_eye_separation changes the distance between the eyes. AnaglyphEffect draws each eye into its own target, and anaglyph_pixel mixes them. color_matrix_left and color_matrix_right are the two matrices. ParallaxBarrierEffect reads the left eye where barrier_row_is_left.
AsciiEffect.render draws the scene and calls asciify. asciify shrinks the image by resolution. Each pixel of every other row of the small image becomes one character. ascii_brightness is three.js's brightness, and ascii_index picks the character. A dark pixel takes a character near the end of the set.
AsciiImage.text returns the characters as lines of plain text. AsciiImage.html returns the markup that three.js writes into its table cell. With color on, each character is a span in its color. block fills its background, and alpha gives it its opacity. An empty character set is three.js's fallback set.
OutlineEffect.render draws the scene with each mesh outlined. The outline material is three.js's GLSL, compiled by compile_shader_material. It draws the back faces, each vertex moved out along its normal in clip space, in one flat color. The rim is then the same width at every distance.
set_parameters(material, parameters) gives the meshes of one material their own OutlineParameters: thickness, color, alpha and visible. This is three.js's userData.outlineParameters. enabled off draws the scene alone.
The outline meshes, their materials and their programs are added to the scene and the assets. They are taken out again when render returns or raises.
-
StereoEffect.renderrefuses a renderer narrower than two pixels.set_eye_separationrefuses a negative separation and keeps the old one. -
AsciiEffectrefuses a resolution that is not finite, or not above zero and at most one.asciifyrefuses an image that shrinks to nothing. -
check_outline_parametersrefuses a thickness or an alpha that is not finite, a negative thickness, and an alpha outside zero to one.set_parametersrefuses a negative material id.
- The ASCII shrink. A browser shrinks the canvas by a filter that it picks. This port averages the pixels whose centers fall in each small pixel, weighed by their alpha.
-
The ASCII output. three.js writes an HTML table into the page. This port returns an
AsciiImagewith its text and its markup. - The outline draw. three.js draws the scene and then the outlines over it. This port draws the scene once with the outline meshes added. The depth test gives the same image for opaque surfaces.
-
What is outlined. Only the meshes in
Scene.mesheswhose geometry has normals. Instanced, batched and skinned meshes, lines, points and sprites are not outlined. -
The outline shader. This port's GLSL knows the built-in matrices only as the steps to
gl_Position. So the shader reads the model-view-projection matrix and its inverse from two uniforms, set for each mesh. - The stereo pair's odd width. For an odd width, three.js's right viewport is one pixel off the image. This port's is not.
The passes run on the host. GpuComposer runs the same composer with the frame on the GPU. See GPU backend.
The SSAA and TAA passes take a clear_color and a clear_alpha, three.js's clearColor and clearAlpha. Without them, each sample clears to the renderer's background. three.js clears to transparent black by default. An SSAA or TAA pass moves the camera's projection by the jitter, after the camera's own view offset. three.js adds the jitter to the view offset instead. The two agree when the view's width and height are the frame's size.
Tutorials
How-to
Reference
- Scene graph
- Rotations
- Cameras
- Geometry
- Geometry addons
- Lofts and clipping groups
- Sculptor
- Procedural generators
- Meshes and assets
- Lines
- Points and sprites
- Helpers
- Scene objects
- Animated objects
- Raycasting
- Curves and paths
- Animation
- Skinning
- Materials
- Node materials
- TSL functions
- Lights
- Lighting addons
- Fog
- Textures
- Renderer
- Renderer hooks and material flags
- Rasterization
- GPU backend
- Render target
- Post-processing
- GPU computation
- Compute nodes
- Progressive light map
- Gaussian splats
- Voxel global illumination
- SVG renderer
- Image files
- Image decode queue
- Model files
- More model files
- Exporters
- Scene JSON
- Windowing and controls
- Math
- Norm consumers
- Math addons
- Units
- Coverage tool
- Commands
- Examples
- Benchmarks
- Extensions
- Shared physics
- Physics query snapshots
- Continuous collision
- Tick force restoration
- Water
- Animals
- Buildings
- CARLA
- Femur
- Tibia
- Fibula
- Patella
- Knee
- Muscles
- Vessels
- Lymph
- Nerves
- Integument
- Leg
- Foot
- Pelvis
- Torso
- Arm
- Hand
- Head
- Genome
- Game humanoid
- Humanoid fidelity
- Audio-aligned game faces
- Converted assets
- Mesh quality
- Segment inertia
Explanation
- Software rasterizer
- Scene graph as an array
- Linear color
- Normals are not colors
- Perspective correction
- Fixed-point coverage
- Per-fragment shading
- Mipmaps
- Sorted transparency
- Types and checks
- Render hooks as a trait
- Shared CPU and GPU code
- Bands and threads
- PNG structure
- Extensions beside the port
- CUDA teardown hang
- Mojo compiler hang