In Shader Graph, the "Scene Color" node (corresponding to _CameraOpaqueTexture in HLSL) is essential for creating effects like glass distortion, water refraction, heat haze, and underwater bubbles. However, when using it to build a refraction shader and setting the material's Surface Type to Transparent, you may encounter serious rendering bugs like these:
- "The material renders completely transparent and doesn't distort the background at all."
- "The shape of the refracting object itself or other transparent objects in front of it are unnaturally reflected on its surface."
- "The refracted image flickers or disappears momentarily when the camera moves."
This article explains the timing of the screen capture texture copy in the URP rendering pipeline, why these issues happen, and how to configure the "Queue Offset" to solve self-reflection and sorting bugs.
1. The Root Cause: Timing of '_CameraOpaqueTexture' Creation in URP
The core issue lies in when the screen capture texture (_CameraOpaqueTexture) is created.
To optimize performance, URP does not copy the screen every frame at arbitrary points. Instead, it copies the buffer at a very specific step in the frame rendering lifecycle:
- Opaque Rendering: All opaque objects (terrain, buildings, character meshes) are rendered to the color buffer.
- Screen Copy (Scene Color Generation): At this exact moment, URP copies the color buffer into
_CameraOpaqueTexture. - Transparent Rendering: After the copy is complete, transparent objects (particles, water, glass, and your refraction shader) are rendered on top of the buffer.
As you can see, "Scene Color only contains opaque objects; transparent objects are not included" by default in URP.
If your refraction shader is rendered in the standard transparent queue (around 3000), it executes after the _CameraOpaqueTexture is copied. However, when multiple transparent objects are sorted (usually by distance from the camera), their rendering order might conflict. If your refractive object renders before another transparent object, that other transparent object won't be captured in the screen buffer. If it renders after, and there are overlaps, the refractive shader might capture its own pixels or cause self-reflection glitches due to buffer synchronization mismatches.
2. The Fix: 3 Essential Configuration Steps
To fix these issues and implement stable, high-quality refraction, configure the following settings correctly.
Step 1: Enable 'Opaque Texture' in the URP Asset
Without this global switch enabled, URP won't capture the screen, and the Scene Color node will return black or stale memory trash.
- Select your Universal Render Pipeline Asset in
Assets/Settings/(and check all additional pipeline assets for different quality settings). - Under General, ensure that Opaque Texture is checked.
- Also, enable Depth Texture if you plan to implement depth-based attenuation for your refraction.
Step 2: Shader Graph Settings
Open your Shader Graph and configure the Graph Settings as follows:
- Material:
Universal - Workflow Mode:
MetallicorSpecular - Surface Type:
Transparent - Render Face:
Front(Front-face only is recommended for refraction to prevent double sampling artifacts on back faces). - Cast Shadows / Receive Shadows: Typically disabled for refractive glass or water.
Step 3: Adjust the Queue Offset (Most Important)
This is the most critical setting for preventing self-reflection and fixing transparent sorting order.
Although transparent objects are sorted by depth, refractive shaders should idealistically render last—after all standard translucent particles and shaders have drawn. Otherwise, transparent particles behind the glass will render in front of it, or won't be refracted.
- In the Graph Settings of your Shader Graph, change the Queue Offset from
0to a positive value, such as100to250. - This shifts the Render Queue of materials using this shader from
3000(Transparent) to3100–3250. - As a result, other transparent objects are drawn first, and your refraction shader can safely sample the completed backbuffer with all transparent elements included.
3. Advanced: Depth-Based Refraction Cutoff via HLSL
Simply distorting Scene Color UVs causes a common visual bug: foreground objects are refracted into the background glass/water (e.g., a character's legs in front of the water surface appear warped inside it). To solve this, compare the scene depth at the distorted sample point with the depth of the refraction surface, and cancel the distortion if the sampled pixel is in front of the surface.
Here is the HLSL function you can use in a Custom Function node:
void ApplyRefractionCutoff_float(
float2 screenUV,
float2 distortion,
float3 positionWS, // Object's world position
float surfaceDepth, // Refraction surface depth (W component of Screen Position (Raw))
out float2 finalUV)
{
// 1. Sample depth at the distorted UV coordinate
float2 distortedUV = screenUV + distortion;
float rawDepth = SHADERGRAPH_SAMPLE_SCENE_DEPTH(distortedUV);
// 2. Convert to linear eye depth
float sceneDepth = LinearEyeDepth(rawDepth, _ZBufferParams);
// 3. Determine if the distorted sample is in front of the refractive surface
// If sceneDepth < surfaceDepth, the sampled pixel is in front of the glass, so cancel the distortion
if (sceneDepth < surfaceDepth)
{
finalUV = screenUV; // Use original undistorted UV
}
else
{
finalUV = distortedUV; // Use distorted UV
}
}
By connecting this Custom Function's finalUV output to the UV input of your Scene Color node, you ensure that foreground objects remain crisp and undistorted, resulting in a professional-grade refraction shader.