Toutes les ressources
After Effectssept. 2026

Fake3DCube

Créé un cube en 2,5D, 100% paramétrique.

martinjude.fr/freebies/fake-3d-cube
script.jsxTélécharger
(function fake3DCube() {
    var SCRIPT_NAME = "Fake 3D Cube";

    var comp = app.project.activeItem;
    if (!(comp && comp instanceof CompItem)) {
        alert("Open a composition (or select one in the Project panel) and run the script again.", SCRIPT_NAME);
        return;
    }

    // Unit cube corners. x right, y down, z away from the viewer.
    var V = [
        [-1, -1, -1], [1, -1, -1], [1, 1, -1], [-1, 1, -1], // front ring (z = -1, closest)
        [-1, -1,  1], [1, -1,  1], [1, 1,  1], [-1, 1,  1]  // back ring
    ];

    // Corners are listed clockwise as seen from outside the cube, so a face
    // whose projected polygon has a positive area is facing the viewer.
    var FACES = [
        { name: "Front",  v: [0, 1, 2, 3], n: [0, 0, -1], color: [0.93, 0.33, 0.33] },
        { name: "Back",   v: [5, 4, 7, 6], n: [0, 0,  1], color: [0.98, 0.62, 0.22] },
        { name: "Left",   v: [4, 0, 3, 7], n: [-1, 0, 0], color: [0.30, 0.75, 0.45] },
        { name: "Right",  v: [1, 5, 6, 2], n: [1, 0,  0], color: [0.25, 0.55, 1.00] },
        { name: "Top",    v: [4, 5, 1, 0], n: [0, -1, 0], color: [0.98, 0.85, 0.30] },
        { name: "Bottom", v: [3, 2, 6, 7], n: [0, 1,  0], color: [0.65, 0.40, 0.90] }
    ];

    var size = Math.round(Math.min(comp.width, comp.height) * 0.25);
    var camDistance = Math.round(Math.max(comp.width, comp.height) * 0.8);

    var CONTROLS = [
        ["slider",   "Width",               size],
        ["slider",   "Height",              size],
        ["slider",   "Depth",               size],
        ["angle",    "Rotation X",          25],
        ["angle",    "Rotation Y",          40],
        ["angle",    "Rotation Z",          0],
        ["slider",   "Camera Distance",     camDistance],
        ["checkbox", "Orthographic",        0],
        ["color",    "Base Color",          [0.25, 0.55, 1.00]],
        ["slider",   "Shading",             45],
        ["angle",    "Light Angle",         -35],
        ["angle",    "Light Elevation",     50],
        ["slider",   "Cube Opacity",        100],
        ["slider",   "Fill Opacity",        100],
        ["color",    "Stroke Color",        [1, 1, 1]],
        ["slider",   "Stroke Width",        0],
        ["checkbox", "Wireframe",           0],
        ["slider",   "Hidden Edge Opacity", 25],
        ["checkbox", "Use Face Colors",     0]
    ];
    for (var f = 0; f < FACES.length; f++) {
        CONTROLS.push(["color", FACES[f].name + " Color", FACES[f].color]);
    }

    var CONTROL_MATCH_NAMES = {
        slider:   "ADBE Slider Control",
        angle:    "ADBE Angle Control",
        checkbox: "ADBE Checkbox Control",
        color:    "ADBE Color Control"
    };

    function layerNameTaken(name) {
        for (var i = 1; i <= comp.numLayers; i++) {
            if (comp.layer(i).name === name) return true;
        }
        return false;
    }

    var cubeIndex = 1;
    while (layerNameTaken("Cube " + cubeIndex + " - Controller")) cubeIndex++;
    var prefix = "Cube " + cubeIndex;
    var ctrlName = prefix + " - Controller";

    // ---------------------------------------------------------------- expressions

    function lines(arr) { return arr.join("\n"); }

    // Controller lookup, shared by every expression.
    var EXPR_HEAD = [
        '// ' + SCRIPT_NAME + ' - driven by "' + ctrlName + '"',
        'var C = thisComp.layer("' + ctrlName + '");',
        'function fx(n) { return C.effect(n)(1).value; }'
    ];

    // 3D rotation: Y (turntable) first, then X (tilt), then Z (roll).
    var EXPR_ROTATE = [
        'var ax = degreesToRadians(fx("Rotation X")), ay = degreesToRadians(fx("Rotation Y")), az = degreesToRadians(fx("Rotation Z"));',
        'function rot(p) {',
        '    var x = p[0], y = p[1], z = p[2], c, s, t;',
        '    c = Math.cos(ay); s = Math.sin(ay); t = x * c + z * s; z = z * c - x * s; x = t;',
        '    c = Math.cos(ax); s = Math.sin(ax); t = y * c - z * s; z = y * s + z * c; y = t;',
        '    c = Math.cos(az); s = Math.sin(az); t = x * c - y * s; y = x * s + y * c; x = t;',
        '    return [x, y, z];',
        '}'
    ];

    // Projected corners of one face (Q), relative to the controller's anchor point.
    function exprCorners(face) {
        var calls = [];
        for (var i = 0; i < 4; i++) {
            var u = V[face.v[i]];
            calls.push("vert(" + u[0] + ", " + u[1] + ", " + u[2] + ")");
        }
        return EXPR_HEAD.concat(EXPR_ROTATE, [
            'var hw = fx("Width") / 2, hh = fx("Height") / 2, hd = fx("Depth") / 2;',
            'var dist = Math.max(fx("Camera Distance"), 1), ortho = fx("Orthographic") > 0;',
            'function vert(x, y, z) {',
            '    var p = rot([x * hw, y * hh, z * hd]);',
            '    var k = ortho ? 1 : dist / Math.max(dist + p[2], 1);',
            '    return [p[0] * k, p[1] * k];',
            '}',
            'var Q = [' + calls.join(", ") + '];'
        ]);
    }

    function exprPath(face) {
        return lines(exprCorners(face).concat([
            'var a = C.transform.anchorPoint.value, pts = [];',
            'for (var i = 0; i < 4; i++) {',
            '    var local = a.length > 2 ? [a[0] + Q[i][0], a[1] + Q[i][1], a[2]] : [a[0] + Q[i][0], a[1] + Q[i][1]];',
            '    var p = fromComp(C.toComp(local));',
            '    pts.push([p[0], p[1]]);',
            '}',
            'createPath(pts, [], [], true);'
        ]));
    }

    // Backface culling. In wireframe mode every edge is shared by two back
    // faces, so each gets 1 - sqrt(1 - h) to land on exactly h once stacked.
    function exprLayerOpacity(face) {
        return lines(exprCorners(face).concat([
            'var area = 0;',
            'for (var i = 0; i < 4; i++) { var j = (i + 1) % 4; area += Q[i][0] * Q[j][1] - Q[j][0] * Q[i][1]; }',
            'var o = clamp(fx("Cube Opacity"), 0, 100);',
            'var hidden = clamp(fx("Hidden Edge Opacity") / 100, 0, 1);',
            'area > 0 ? o : (fx("Wireframe") > 0 ? o * (1 - Math.sqrt(1 - hidden)) : 0);'
        ]));
    }

    function exprFillColor(face) {
        return lines(EXPR_HEAD.concat(EXPR_ROTATE, [
            'var n = rot([' + face.n.join(", ") + ']);',
            'var la = degreesToRadians(fx("Light Angle")), le = degreesToRadians(fx("Light Elevation"));',
            'var L = [Math.sin(la) * Math.cos(le), -Math.sin(le), -Math.cos(la) * Math.cos(le)];',
            'var lambert = Math.max(0, n[0] * L[0] + n[1] * L[1] + n[2] * L[2]);',
            'var k = 1 - clamp(fx("Shading") / 100, 0, 1) * (1 - lambert);',
            'var c = fx("Use Face Colors") > 0 ? fx("' + face.name + ' Color") : fx("Base Color");',
            '[c[0] * k, c[1] * k, c[2] * k, 1];'
        ]));
    }

    var EXPR_FILL_OPACITY = lines(EXPR_HEAD.concat([
        'fx("Wireframe") > 0 ? 0 : fx("Fill Opacity");'
    ]));

    // A 1px stroke in the fill color hides the hairline gaps anti-aliasing
    // leaves between adjacent faces. Only used when faces are fully opaque.
    var EXPR_SEAM_COLOR = 'content("Face").content("Face Fill")("ADBE Vector Fill Color").value;';
    var EXPR_SEAM_OPACITY = lines(EXPR_HEAD.concat([
        'fx("Wireframe") > 0 || fx("Fill Opacity") < 100 ? 0 : 100;'
    ]));

    var EXPR_STROKE_COLOR = lines(EXPR_HEAD.concat([
        'fx("Stroke Color");'
    ]));
    var EXPR_STROKE_WIDTH = lines(EXPR_HEAD.concat([
        'var w = fx("Stroke Width");',
        'fx("Wireframe") > 0 && w <= 0 ? 2 : w;'
    ]));

    // ---------------------------------------------------------------- build

    function addControl(layer, type, name, value) {
        var effect = layer.property("ADBE Effect Parade").addProperty(CONTROL_MATCH_NAMES[type]);
        effect.name = name;
        effect.property(1).setValue(type === "color" ? [value[0], value[1], value[2], 1] : value);
    }

    // Re-fetch shape contents by name each time: adding a property invalidates
    // ExtendScript references to its siblings.
    function faceContents(layer) {
        return layer.property("ADBE Root Vectors Group").property("Face").property("ADBE Vectors Group");
    }

    function buildFace(face) {
        var layer = comp.layers.addShape();
        layer.name = prefix + " - " + face.name;

        var group = layer.property("ADBE Root Vectors Group").addProperty("ADBE Vector Group");
        group.name = "Face";

        // Stacking inside the group, top to bottom: path, edge stroke, seam stroke, fill.
        faceContents(layer).addProperty("ADBE Vector Shape - Group").name = "Face Path";
        faceContents(layer).addProperty("ADBE Vector Graphic - Stroke").name = "Edge Stroke";
        faceContents(layer).addProperty("ADBE Vector Graphic - Stroke").name = "Seam Fix";
        faceContents(layer).addProperty("ADBE Vector Graphic - Fill").name = "Face Fill";

        var items = faceContents(layer);
        items.property("Face Path").property("ADBE Vector Shape").expression = exprPath(face);

        var edge = items.property("Edge Stroke");
        edge.property("ADBE Vector Stroke Color").expression = EXPR_STROKE_COLOR;
        edge.property("ADBE Vector Stroke Width").expression = EXPR_STROKE_WIDTH;
        edge.property("ADBE Vector Stroke Line Cap").setValue(2);  // round
        edge.property("ADBE Vector Stroke Line Join").setValue(2); // round

        var seam = items.property("Seam Fix");
        seam.property("ADBE Vector Stroke Width").setValue(1);
        seam.property("ADBE Vector Stroke Line Join").setValue(2);
        seam.property("ADBE Vector Stroke Color").expression = EXPR_SEAM_COLOR;
        seam.property("ADBE Vector Stroke Opacity").expression = EXPR_SEAM_OPACITY;

        var fill = items.property("Face Fill");
        fill.property("ADBE Vector Fill Color").expression = exprFillColor(face);
        fill.property("ADBE Vector Fill Opacity").expression = EXPR_FILL_OPACITY;

        layer.property("ADBE Transform Group").property("ADBE Opacity").expression = exprLayerOpacity(face);
        return layer;
    }

    app.beginUndoGroup("Create " + SCRIPT_NAME);
    try {
        // Controller first so the face expressions never point at a missing layer.
        var ctrl = comp.layers.addNull();
        ctrl.name = ctrlName;
        for (var c = 0; c < CONTROLS.length; c++) {
            addControl(ctrl, CONTROLS[c][0], CONTROLS[c][1], CONTROLS[c][2]);
        }

        // Layer order between faces doesn't matter: only the front faces are
        // visible and they never overlap.
        for (var i = 0; i < FACES.length; i++) buildFace(FACES[i]);
        ctrl.moveToBeginning();

        var selected = comp.selectedLayers;
        for (var s = 0; s < selected.length; s++) selected[s].selected = false;
        ctrl.selected = true;
    } catch (err) {
        alert("Could not build the cube:\n" + err.toString() + (err.line ? " (line " + err.line + ")" : ""), SCRIPT_NAME);
    } finally {
        app.endUndoGroup();
    }
})();
what_it_does.md

Fake 3D Cube for After Effects

Builds a parametric "fake 3D" cube in the active composition:

  • 6 ordinary 2D shape layers, one per face, driven by expressions
  • 1 null "Controller" layer that holds every parameter as Effect Controls

No 3D layers, cameras or plugins: the eight corners are rotated and perspective-projected inside expressions, faces pointing away from the viewer are hidden (backface culling), and each face is shaded from a virtual directional light.

HOW TO RUN

Open a composition, then File > Scripts > Run Script File... and pick this file. To get it in the menu, copy it to: /Applications/Adobe After Effects <version>/Scripts/ Run it again to add more cubes (Cube 2, Cube 3, ...).

CONTROLLER EFFECTS

  Width / Height / Depth ..... size of the cube, in pixels
  Rotation X / Y / Z ......... Y spins the cube (turntable), X tilts it, Z rolls it
  Camera Distance ............ lower = stronger perspective
  Orthographic ............... disables perspective (isometric look)
  Base Color ................. face color
  Shading .................... how much darker faces turned away from the light get (0 = flat)
  Light Angle / Elevation .... where the light comes from (0 / 0 = from the viewer)
  Cube Opacity ............... fades the whole cube
  Fill Opacity ............... face fill opacity
  Stroke Color / Width ....... edge outline (0 = no outline)
  Wireframe .................. edges only, back edges included
  Hidden Edge Opacity ........ opacity of the back edges in wireframe mode
  Use Face Colors ............ use the six per-face colors below instead of Base Color
  Front ... Bottom Color ..... per-face colors

The null's own transform drives the cube too: move it to place the cube, scale it to scale the cube, rotate it for a flat 2D rotation. It can be parented and animated like any layer. (The null's Opacity is not used: recent After Effects versions report it as 0 for null objects.)

2-5D_cube.png
3Dcube_free.mp4