diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts
index 67d7ac11c0dd1b49c0a5fc9af2d666fc579189a2..c2b5b3925a412b23955760131c5ee07c9c0dcac2 100644
--- a/src/mol-gl/_spec/renderer.spec.ts
+++ b/src/mol-gl/_spec/renderer.spec.ts
@@ -138,7 +138,7 @@ describe('renderer', () => {
 
         scene.add(points)
         expect(ctx.bufferCount).toBe(4);
-        expect(ctx.textureCount).toBe(4);
+        expect(ctx.textureCount).toBe(5);
         expect(ctx.vaoCount).toBe(4);
         expect(ctx.programCache.count).toBe(4);
         expect(ctx.shaderCache.count).toBe(8);
diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts
index 7c6831489246a6aebe4c38633518c05a211fe5f4..a05756c6392b7fc26cc37d3f4c7aa408f33ef6a0 100644
--- a/src/mol-gl/renderer.ts
+++ b/src/mol-gl/renderer.ts
@@ -177,7 +177,7 @@ namespace Renderer {
                 gl.depthMask(true)
                 for (let i = 0, il = renderables.length; i < il; ++i) {
                     const r = renderables[i]
-                    if (r.state.opaque) renderObject(r, variant)
+                    if (r.state.opaque && !r.values.dTransparency.ref.value) renderObject(r, variant)
                 }
 
                 gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
@@ -185,7 +185,7 @@ namespace Renderer {
                 for (let i = 0, il = renderables.length; i < il; ++i) {
                     const r = renderables[i]
                     gl.depthMask(r.values.uAlpha.ref.value === 1.0)
-                    if (!r.state.opaque) renderObject(r, variant)
+                    if (!r.state.opaque || r.values.dTransparency.ref.value) renderObject(r, variant)
                 }
             } else {
                 // picking
diff --git a/src/mol-gl/shader/chunks/assign-color-varying.glsl b/src/mol-gl/shader/chunks/assign-color-varying.glsl
index e820a5849227b98f5c074bdadd7df819376fafa5..3aff425ffe42230481d5b75bdf3da9d0577a6802 100644
--- a/src/mol-gl/shader/chunks/assign-color-varying.glsl
+++ b/src/mol-gl/shader/chunks/assign-color-varying.glsl
@@ -19,5 +19,5 @@
 #endif
 
 #ifdef dTransparency
-    vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + aGroup, uTransparencyTexDim);
+    vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + aGroup, uTransparencyTexDim).a;
 #endif
\ No newline at end of file
diff --git a/src/mol-gl/shader/chunks/assign-material-color.glsl b/src/mol-gl/shader/chunks/assign-material-color.glsl
index bd35c1fcb6e4a3122b36c29c5991ede2326781a8..56e406dcedb22bc3b379ab39d045f9e925ad1255 100644
--- a/src/mol-gl/shader/chunks/assign-material-color.glsl
+++ b/src/mol-gl/shader/chunks/assign-material-color.glsl
@@ -13,5 +13,40 @@
 
 // apply transparency
 #if defined(dTransparency) && (defined(dColorType_uniform) || defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance))
-    material.a *= 1 - vTransparency;
+    float ma = material.a * (1.0 - vTransparency);
+    ivec2 pixelCoord = ivec2(gl_FragCoord.xy);
+
+    // const mat4 thresholdMatrix = mat4(
+    //     1.0 / 17.0,  9.0 / 17.0,  3.0 / 17.0, 11.0 / 17.0,
+    //     13.0 / 17.0,  5.0 / 17.0, 15.0 / 17.0,  7.0 / 17.0,
+    //     4.0 / 17.0, 12.0 / 17.0,  2.0 / 17.0, 10.0 / 17.0,
+    //     16.0 / 17.0,  8.0 / 17.0, 14.0 / 17.0,  6.0 / 17.0
+    // );
+    // float at = thresholdMatrix[pixelCoord.x % 4][pixelCoord.y % 4];
+
+    // https://research.nvidia.com/publication/hashed-alpha-testing
+    // Find the discretized derivatives of our coordinates
+    float maxDeriv = max(length(dFdx(vViewPosition)), length(dFdy(vViewPosition)));
+    float pixScale = 1.0 / maxDeriv;
+    // Find two nearest log-discretized noise scales
+    vec2 pixScales = vec2(exp2(floor(log2(pixScale))), exp2(ceil(log2(pixScale))));
+    // Compute alpha thresholds at our two noise scales
+    vec2 alpha = vec2(hash3d(floor(pixScales.x * vViewPosition)), hash3d(floor(pixScales.y * vViewPosition)));
+    // Factor to interpolate lerp with
+    float lerpFactor = fract(log2(pixScale));
+    // Interpolate alpha threshold from noise at two scales
+    float x = (1.0 - lerpFactor) * alpha.x + lerpFactor * alpha.y;
+    // Pass into CDF to compute uniformly distrib threshold
+    float a = min(lerpFactor, 1.0 - lerpFactor);
+    vec3 cases = vec3(
+        x * x / (2.0 * a * (1.0 - a)),
+        (x - 0.5 * a) / (1.0 - a),
+        1.0 - ((1.0 - x) * (1.0 - x) / (2.0 * a * (1.0 - a)))
+    );
+    // Find our final, uniformly distributed alpha threshold
+    float at = (x < (1.0 - a)) ? ((x < a) ? cases.x : cases.y) : cases.z;
+    // Avoids ατ == 0. Could also do
+    at = clamp(at, 1.0e-6, 1.0);
+
+    if (ma < 0.99 && (ma < 0.01 || ma < at)) discard;
 #endif
\ No newline at end of file
diff --git a/src/mol-gl/shader/chunks/common.glsl b/src/mol-gl/shader/chunks/common.glsl
index c26150ec86632fe48034fa8e6265a42d375ed4eb..b10d6d621574588d3c2eb829217d5e69b20902e0 100644
--- a/src/mol-gl/shader/chunks/common.glsl
+++ b/src/mol-gl/shader/chunks/common.glsl
@@ -1,6 +1,14 @@
 float intDiv(float a, float b) { return float(int(a) / int(b)); }
 float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
 
+float hash2d(vec2 coord2d) {
+    return fract(1.0e4 * sin(17.0 * coord2d.x + 0.1 * coord2d.y) * (0.1 + abs(sin(13.0 * coord2d.y + coord2d.x))));
+}
+
+float hash3d(vec3 coord3d) {
+    return hash2d(vec2(hash2d(coord3d.xy), coord3d.z));
+}
+
 #if __VERSION__ != 300
     // transpose
 
diff --git a/src/mol-repr/structure/complex-representation.ts b/src/mol-repr/structure/complex-representation.ts
index da59074da1cf8f6c0b2c6978cf93581f1fbcb887..e8ed4daf473e96be54a04d54cd67e357f1c6a329 100644
--- a/src/mol-repr/structure/complex-representation.ts
+++ b/src/mol-repr/structure/complex-representation.ts
@@ -69,6 +69,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
         if (state.alphaFactor !== undefined && visual) visual.setAlphaFactor(state.alphaFactor)
         if (state.pickable !== undefined && visual) visual.setPickable(state.pickable)
         if (state.overpaint !== undefined && visual) visual.setOverpaint(state.overpaint)
+        if (state.transparency !== undefined && visual) visual.setTransparency(state.transparency)
         if (state.transform !== undefined && visual) visual.setTransform(state.transform)
         if (state.unitTransforms !== undefined && visual) {
             // Since ComplexVisuals always renders geometries between units the application of `unitTransforms`
diff --git a/src/mol-repr/structure/complex-visual.ts b/src/mol-repr/structure/complex-visual.ts
index aeb4752bd5415d26610e43f10cc3b77c779170c4..0cc0bf9274c6ba4484e871bcc87300770119b993 100644
--- a/src/mol-repr/structure/complex-visual.ts
+++ b/src/mol-repr/structure/complex-visual.ts
@@ -197,7 +197,7 @@ export function ComplexVisual<G extends Geometry, P extends ComplexParams & Geom
         setTransform(matrix?: Mat4, instanceMatrices?: Float32Array | null) {
             Visual.setTransform(renderObject, matrix, instanceMatrices)
         },
-        setOverpaint(overpaint: Overpaint, clear = false) {
+        setOverpaint(overpaint: Overpaint) {
             return Visual.setOverpaint(renderObject, overpaint, lociApply, true)
         },
         setTransparency(transparency: Transparency) {
diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts
index 8985cb7750405864cae3d68ab9f7d158380c6c3c..b54a84c252131a13e1af0c16612e026a309a731b 100644
--- a/src/mol-repr/structure/units-representation.ts
+++ b/src/mol-repr/structure/units-representation.ts
@@ -172,11 +172,12 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R
     }
 
     function setState(state: Partial<StructureRepresentationState>) {
-        const { visible, alphaFactor, pickable, overpaint, transform, unitTransforms } = state
+        const { visible, alphaFactor, pickable, overpaint, transparency, transform, unitTransforms } = state
         if (visible !== undefined) visuals.forEach(({ visual }) => visual.setVisibility(visible))
         if (alphaFactor !== undefined) visuals.forEach(({ visual }) => visual.setAlphaFactor(alphaFactor))
         if (pickable !== undefined) visuals.forEach(({ visual }) => visual.setPickable(pickable))
         if (overpaint !== undefined) visuals.forEach(({ visual }) => visual.setOverpaint(overpaint))
+        if (transparency !== undefined) visuals.forEach(({ visual }) => visual.setTransparency(transparency))
         if (transform !== undefined) visuals.forEach(({ visual }) => visual.setTransform(transform))
         if (unitTransforms !== undefined) {
             visuals.forEach(({ visual, group }) => {
diff --git a/src/mol-repr/visual.ts b/src/mol-repr/visual.ts
index 3675e810816ce7393075864f2d3b25004f93a3c6..0e120b6e6d887939043f1c319bb2272e201c90ae 100644
--- a/src/mol-repr/visual.ts
+++ b/src/mol-repr/visual.ts
@@ -97,6 +97,7 @@ namespace Visual {
             lociApply(loci, apply)
         }
         ValueCell.update(tOverpaint, tOverpaint.ref.value)
+        console.log(renderObject)
     }
 
     export function setTransparency(renderObject: GraphicsRenderObject | undefined, transparency: Transparency, lociApply: LociApply, clear: boolean) {
diff --git a/src/mol-repr/volume/representation.ts b/src/mol-repr/volume/representation.ts
index fffc5461d14d62489e3c876816c306d75157a44d..621c17ebcf8fb4e07924573f8b544e7e2fa28291 100644
--- a/src/mol-repr/volume/representation.ts
+++ b/src/mol-repr/volume/representation.ts
@@ -254,6 +254,7 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
         if (state.alphaFactor !== undefined && visual) visual.setAlphaFactor(state.alphaFactor)
         if (state.pickable !== undefined && visual) visual.setPickable(state.pickable)
         if (state.overpaint !== undefined && visual) visual.setOverpaint(state.overpaint)
+        if (state.transparency !== undefined && visual) visual.setTransparency(state.transparency)
         if (state.transform !== undefined && visual) visual.setTransform(state.transform)
 
         Representation.updateState(_state, state)