Angeldust 3D Anaglyph
Platform Requirement: This script works on PC only (Tested in Windows, macOS and Linux I have no idea but it should work).
How to Install
Install a Userscript Manager: Open Google Chrome and install the Tampermonkey extension from the Chrome Web Store. Create a New Script: Click the Tampermonkey icon in your browser toolbar. Select Create a new script.
Paste the Code: Delete any default text in the editor. Copy the entire script code provided bellow. Paste it into the Tampermonkey editor.
Save: Press Ctrl + S or go to File > Save within the Tampermonkey editor.
How to Use
Launch the Game: Navigate to https://angeldu.st/static/webgl-new/. Open the Menu: You will see a small button labeled 3D in the top-left corner of the screen. Enable Effect: Click the 3D button to open the settings panel. Check the Enable 3D Effect box. Hardware: You must wear standard Red-Cyan 3D glasses to see the effect. Adjust Settings: Use the sliders to change "Eye Separation" and "Depth Curve" to match your screen size and comfort level.
// ==UserScript==
// @name Angeldust 3D Anaglyph
// @namespace http://tampermonkey.net/
// @version 3.0
// @description Adds stereoscopic red-cyan 3D effect
// @author Ripie and a select number of artificial sweeteners
// @match https://angeldu.st/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
console.log("Angeldust 3D Anaglyph (Depth-Based): Initializing...");
let glContext = null;
let activeProgram = null;
let depthTexture = null;
let currentFramebuffer = null;
let settings = {
enabled: true,
separation: 10.0, // Pixel offset
depthPower: -0.2, // Depth curve
debugDepth: false,
menuVisible: false
};
// --- UI SETUP ---
function createUI() {
if (document.getElementById('ad-3d-ui')) return;
const toggleBtn = document.createElement('div');
Object.assign(toggleBtn.style, {
position: 'fixed', top: '10px', left: '10px', zIndex: '100000',
width: '30px', height: '30px', background: '#333', color: '#f00',
border: '1px solid #0ff', borderRadius: '4px', cursor: 'pointer',
display: 'flex', justifyContent: 'center', alignItems: 'center',
fontSize: '20px', fontWeight: 'bold'
});
toggleBtn.innerText = '3D';
toggleBtn.onclick = () => {
settings.menuVisible = !settings.menuVisible;
mainPanel.style.display = settings.menuVisible ? 'block' : 'none';
};
document.body.appendChild(toggleBtn);
const mainPanel = document.createElement('div');
mainPanel.id = 'ad-3d-ui';
Object.assign(mainPanel.style, {
position: 'fixed', top: '50px', left: '10px', zIndex: '99999',
backgroundColor: 'rgba(10, 10, 10, 0.95)', padding: '10px',
color: '#0ff', border: '2px solid #0ff', fontFamily: 'monospace',
width: '260px', borderRadius: '8px', display: 'none'
});
const title = document.createElement('div');
title.innerText = "3D ANAGLYPH (DEPTH)";
title.style.textAlign = "center";
title.style.fontWeight = "bold";
title.style.marginBottom = "10px";
title.style.color = "#f00";
title.style.textShadow = "2px 2px #0ff";
mainPanel.appendChild(title);
// Enable Toggle
const toggleRow = document.createElement('div');
toggleRow.style.marginBottom = '10px';
const toggleCheck = document.createElement('input');
toggleCheck.type = 'checkbox';
toggleCheck.checked = settings.enabled;
toggleCheck.onchange = () => { settings.enabled = toggleCheck.checked; };
const toggleLabel = document.createElement('label');
toggleLabel.innerText = ' Enable 3D Effect';
toggleLabel.style.fontSize = '12px';
toggleRow.appendChild(toggleCheck);
toggleRow.appendChild(toggleLabel);
mainPanel.appendChild(toggleRow);
// Debug Toggle
const debugRow = document.createElement('div');
debugRow.style.marginBottom = '10px';
const debugCheck = document.createElement('input');
debugCheck.type = 'checkbox';
debugCheck.checked = settings.debugDepth;
debugCheck.onchange = () => { settings.debugDepth = debugCheck.checked; };
const debugLabel = document.createElement('label');
debugLabel.innerText = ' Debug Depth View';
debugLabel.style.fontSize = '12px';
debugLabel.style.color = '#ff0';
debugRow.appendChild(debugCheck);
debugRow.appendChild(debugLabel);
mainPanel.appendChild(debugRow);
// Sliders
const addSlider = (label, key, min, max, scale) => {
const row = document.createElement('div');
row.style.marginBottom = '8px';
const txt = document.createElement('div');
txt.style.fontSize = '11px';
const range = document.createElement('input');
range.type = 'range';
range.min = min;
range.max = max;
range.style.width = '100%';
const update = () => {
settings[key] = parseInt(range.value) / scale;
txt.innerText = `${label}: ${(settings[key]).toFixed(1)}`;
};
range.value = settings[key] * scale;
range.oninput = update;
update();
row.appendChild(txt);
row.appendChild(range);
mainPanel.appendChild(row);
};
addSlider("Eye Separation", "separation", -100, 100, 10.0);
addSlider("Depth Curve", "depthPower", -10.0, 50, 10.0);
const info = document.createElement('div');
info.style.marginTop = '10px';
info.style.fontSize = '10px';
info.style.borderTop = '1px solid #333';
info.style.paddingTop = '8px';
info.innerHTML = 'DEPTH-BASED MODE
Uses GL depth buffer.
Use red-cyan glasses
(Red=Left, Cyan=Right)';
mainPanel.appendChild(info);
document.body.appendChild(mainPanel);
}
const loader = () => {
if (!document.body) requestAnimationFrame(loader);
else createUI();
};
if (document.readyState === 'complete') loader();
else window.addEventListener('load', loader);
// --- WEBGL HOOKS ---
const gl = WebGLRenderingContext.prototype;
const _shaderSource = gl.shaderSource;
const _useProgram = gl.useProgram;
const _drawElements = gl.drawElements;
const _getUniform = gl.getUniformLocation;
const _uniform1f = gl.uniform1f;
const _uniform1i = gl.uniform1i;
const _bindFramebuffer = gl.bindFramebuffer;
const _activeTexture = gl.activeTexture;
const _bindTexture = gl.bindTexture;
// Track framebuffer to know when we're rendering to screen
gl.bindFramebuffer = function(target, framebuffer) {
currentFramebuffer = framebuffer;
return _bindFramebuffer.call(this, target, framebuffer);
};
// --- SHADER INJECTION ---
gl.shaderSource = function(shader, source) {
let modified = source;
// TARGET
if (source.includes("AD_SHADER_IS_SCREEN_COMPOSITING") && source.includes("void main()")) {
if (!modified.includes("uniform float u_3D_Enabled;")) {
const header = "precision mediump float;";
modified = modified.replace(header,
header + "\n" +
"uniform float u_3D_Enabled;\n" +
"uniform float u_3D_Separation;\n" +
"uniform float u_3D_DepthPower;\n" +
"uniform float u_3D_DebugDepth;\n" +
"uniform sampler2D u_3D_DepthTexture;\n");
}
const finalOutputRegex = /(gl_FragColor\s*=\s*vec4\(linearToSRGB\(renderColor \+ postProcessed\),\s*1\.0\);)/;
if (finalOutputRegex.test(modified)) {
modified = modified.replace(finalOutputRegex, `
// --- 3D ANAGLYPH POST-PROCESS ---
vec3 finalColor = renderColor + postProcessed;
if (u_3D_DebugDepth > 0.5) {
// Debug: Show OpenGL depth buffer
float rawDepth = texture2D(u_3D_DepthTexture, uv).r;
// Linearize depth for visualization
float near = 0.1;
float far = 1000.0;
float linearDepth = (2.0 * near) / (far + near - rawDepth * (far - near));
finalColor = vec3(linearDepth * 10.0); // Scale for visibility
}
else if (u_3D_Enabled > 0.5) {
float rawDepth = texture2D(u_3D_DepthTexture, uv).r;
float near = 0.1;
float far = 1000.0;
float linearDepth = (2.0 * near) / (far + near - rawDepth * (far - near));
float depth = pow(clamp(linearDepth, 0.0, 1.0), u_3D_DepthPower);
float pixelOffset = (u_3D_Separation / uniform_TextureSize.x) * (1.0 - depth);
vec2 leftUV = uv + vec2(pixelOffset, 0.0);
vec3 leftColor = hdrToSDR(texture2D(uniform_TextureMaterial, leftUV));
vec3 leftPost = dualFilterUpsample(uniform_TexturePostProcessed, leftUV, halfpixel);
vec2 rightUV = uv - vec2(pixelOffset, 0.0);
vec3 rightColor = hdrToSDR(texture2D(uniform_TextureMaterial, rightUV));
vec3 rightPost = dualFilterUpsample(uniform_TexturePostProcessed, rightUV, halfpixel);
finalColor = vec3(
(leftColor.r + leftPost.r) * 1.1,
(rightColor.g + rightPost.g),
(rightColor.b + rightPost.b)
);
}
gl_FragColor = vec4(linearToSRGB(finalColor), 1.0);
// --- END 3D ANAGLYPH ---
`);
}
}
return _shaderSource.call(this, shader, modified);
};
// Update uniforms and bind depth texture
gl.useProgram = function(program) {
const result = _useProgram.call(this, program);
activeProgram = program;
if (program && !program.has3DLocs) {
program.u_3D_Enabled = _getUniform.call(this, program, "u_3D_Enabled");
program.u_3D_Separation = _getUniform.call(this, program, "u_3D_Separation");
program.u_3D_DepthPower = _getUniform.call(this, program, "u_3D_DepthPower");
program.u_3D_DebugDepth = _getUniform.call(this, program, "u_3D_DebugDepth");
program.u_3D_DepthTexture = _getUniform.call(this, program, "u_3D_DepthTexture");
if (program.u_3D_Enabled) {
console.log("3D uniforms bound to post-processing shader");
}
program.has3DLocs = true;
glContext = this;
}
return result;
};
gl.drawElements = function(mode, count, type, offset) {
if (activeProgram && activeProgram.u_3D_Enabled) {
// Only apply to final screen render (framebuffer = null)
if (currentFramebuffer === null) {
_uniform1f.call(this, activeProgram.u_3D_DebugDepth, settings.debugDepth ? 1.0 : 0.0);
_uniform1f.call(this, activeProgram.u_3D_Enabled, settings.enabled ? 1.0 : 0.0);
if (settings.enabled || settings.debugDepth) {
_uniform1f.call(this, activeProgram.u_3D_Separation, settings.separation);
_uniform1f.call(this, activeProgram.u_3D_DepthPower, settings.depthPower);
// Bind depth texture to texture unit 7
if (activeProgram.u_3D_DepthTexture) {
_activeTexture.call(this, this.TEXTURE7);
_bindTexture.call(this, this.TEXTURE_2D, depthTexture);
_uniform1i.call(this, activeProgram.u_3D_DepthTexture, 7);
_activeTexture.call(this, this.TEXTURE0);
}
}
}
}
return _drawElements.call(this, mode, count, type, offset);
};
// Capture the depth texture when it's created
const _texImage2D = gl.texImage2D;
gl.texImage2D = function(target, level, internalformat, width, height, border, format, type, pixels) {
const result = _texImage2D.apply(this, arguments);
// Try to identify the depth texture
// Depth textures are usually DEPTH_COMPONENT format
if (format === this.DEPTH_COMPONENT || format === this.DEPTH_STENCIL) {
const boundTex = this.getParameter(this.TEXTURE_BINDING_2D);
if (boundTex) {
depthTexture = boundTex;
console.log("Captured depth texture:", depthTexture);
}
}
return result;
};
})();