Rendering
Shell texturing vs raymarching
Sep 3, 2025
10 minutes
Shell texturing is a rendering method that renders multiple layers of a surface with an offset to add depth or nuance extending from the surface. I will explain what is the main problem with shell texturing and what are the options to optimize it.
Examples
Some games use shell texturing to render effects like fog, fur, grass, energy shields, or similar visuals.

:image-description:
Biomutant used shell texturing to render fur. Porky Puff has visible shell texturing artifacts, where you can see various shell layers. If the game is good, who cares about the artifacts?

:image-description:
Kerbal Space Program used shell texturing for the reentry heating effect. The game renders the spaceship, then renders it multiple times as distorted shell layers to draw the flames.
How it works
Shell rendering usually works by rendering the object multiple times, each time offsetting vertices slightly along the normals. This makes the object look like it has a surrounding shell with an effect.
I implemented volumetric fog using shell texturing to show what is possible with this rendering technique.
:center-px:

:image-description:
Using 5 shell layers to draw volumetric fog. You can see 5 quads are drawn.
:center-px:

:image-description:
Using 10 shell layers to draw volumetric fog. You can see 10 quads are drawn.
:center-px:

:image-description:
Using 40 shell layers to draw volumetric fog. You can see 40 quads are drawn. It got dense.
The effect can be achieved in multiple ways. Here are some of my ideas:
1. Prepare a separate mesh. Duplicate the mesh vertices as many times as many layers you want to render. Add the additional "shell offset" attribute to each vertex and make it different for each layer.
2. Render single mesh many times, each time with different shader properties.
3. Render a mesh using instancing. Render as many instances as many layers you want to have. Use SV_Instance attribute in your vertex shader as the shell ID.
4. Use geometry shader to render an original mesh. For each rendered triangle in a mesh spawn additional "shell" triangles.
___
Shell texturing performance
Shell texturing is a memory-intensive rendering method. For each layer, the GPU computes a color and blends it with the color buffer. Each pixel of each layer uses memory bandwidth to read the color buffer, blend the color, and write the value back.
With shell texturing, drawing more layers usually results in better image quality but lower performance. Let's measure its performance on RTX 3060.
Let's look at this scene:

:image-description:
Scene with animated volumetric fog trails that use 20 additive transparent layers.
When profiled with Nvidia Nsight Graphics GPU Trace Profiler on RTX 3060 at Full HD, this fog took 1.12ms to render on average (measured over 10 frames). It is bottlenecked by the Screen Pipe, which handles blending colors into the color buffer.
:center-px:

:image-description:
Profiling results of shell-textured volumetric fog. Screen Pipe is the main bottleneck because each layer needs to blend color into the color buffer.
I expect this rendering technique will be slower on GPUs with lower memory bandwidth. How can I optimize it?
How can it be optimized?
Let's think about how shell texturing can be optimized. You can always think about:
Reducing the layer count.
Reducing the size of a rendered object on the screen. Shell texturing performance depends on the number of covered pixels. For cinematics, it may help to change some camera angles.
Those are basic ideas. Let's consider some edge cases that are common in real applications.
Rendering shells without any offset
In some cases, I want to render the same object with transparency many times to increase the effect intensity. Imagine a nice energy shield effect. It is tempting to duplicate the hemisphere, create a new material with different parameters, and render it 2-4 times to bump the effect.
However, duplicating transparent objects will double the rendered pixels and use VRAM bandwidth. If the object is large, it will slow down the rendering, especially on low-end devices.
How to optimize it?
Don't render the effect multiple times. Store the effect parameters in a buffer and use a for loop inside the shader to encapsulate all the logic inside a single fragment shader.
Single shader execution will handle all the layers, but it will write to the screen only once!
Here is some pseudo code that illustrates the idea.

:image-description:
Example layered effect. The same effect is stacked multiple times with different parameters. Check this shader in action here: https://www.shadertoy.com/view/3cByRd
Layers can be stacked in the shader code.
:image-description:
This example uses a lot of magic numbers. You usually want to expose those as parameters. I created this shader solely for this article to explain the idea.
In-shader raytracing
In many scenarios where shell texturing is used on a plane, sphere, or terrain, it is possible to implement it using ray marching. It works like shell texturing, but all logic for rendering multiple offset layers is contained within a single fragment shader. As in the above example, the shader also handles the layer's geometry.

:image-description:
Replacing shell texturing with raymarching is not always possible. When it is, you reduce the cost of blending colors but increase the cost of raytracing layers in the fragment code. From my experience, marching or raytracing in the shader is more efficient than shell texturing.
This is what we need to do in a fragment shader code to make it work in the same way:
Handle the geometry of each layer (ex., using raycasting or raymarching)
Depth testing with the opaque scene
Accumulating the colors from multiple layers.
Original shell texturing shader
Let's look at the original shader that assumes that all the shell layers are included within a rendered mesh.
Look at the fragment shader. It is super simple. It only computes the noise and outputs a color to the screen. No complex logic there. The whole shell logic happens in the vertex shader. GPU renders multiple layers and does depth testing and color blending for us.
This is the effect:
I calculate the fog in object space. This allows me to adjust the fog position and compose it using objects in the hierarchy.
Optimized raymarching shader
Now, let's look at the optimized raymarched shader. First of all, I used a cube mesh, so I needed to adjust it a little to match the bounding box of all the shells. This is a vertex shader:
Notice that I do not offset the vertices along the normals. The shader just draws the interior faces of a cube.
The key work happens in the fragment shader, which handles depth testing, raycasting layers, and accumulating the color.

The fragment shader code is explained using the comments:
This code is much more complex than usual shell texturing.
Let's compare the performance of shell texturing versus ray marching. The same scene, the same camera angle.

:image-description:
Measured on RTX 3060.
The raymarching shader rendered more than twice as fast. The bottleneck shifted from Screen Pipe (color blending) to SM units (shader code execution), providing almost the same visuals. The color was slightly different because I did not tweak it accurately.
:image-description:
Both methods yield the same visuals, but raymarching renders more than twice as fast.
Summary
Shell texturing is a shading technique that renders multiple offset layers of a surface to create depth and nuanced visual effects like fur, fog, grass, or energy shields.
When to use shell texturing:
For effects that need visible, discrete layers.
When you need a quick prototype or want to leverage GPU hardware blending instead of writing complex shaders.
On target devices where overdraw and memory bandwidth aren't a major bottleneck.
When to avoid shell texturing:
Volumetric effects (fog, fire, smoke, plasma), where continuous density matters - raymarching is faster and smoother.
On fill-rate-limited devices (low-end consoles, mobile, older GPUs)
For on-screen large effects - better to consolidate logic into a single shader pass. Avoid complex shell texturing effects being displayed on the whole screen.
Trade-offs:
More layers = higher visual fidelity, but also heavy bandwidth usage and overdraw.
Raymarching and in-shader accumulation give similar results at much lower cost - shifting the bottleneck to shader ALU instead of memory bandwidth.
Shell texturing is simple to implement and can be implemented using various approaches, but it doesn't fix the fill-rate problem.
Read more:

Shell texturing uses a lot of VRAM bandwidth. Read what VRAM bandwidth is and why it is important to care about it during game development:
https://www.proceduralpixels.com/blog/vram-bandwidth-and-its-big-role-in-optimization
You can find all my blogs here:
https://www.proceduralpixels.com/blog
In case you use linkedin, this is a post related to this article. Feel free to ask me a question there:
Link to a linkedin post