Peppers Ghost Hologram Effects for Terrain

peppers ghost terrain holographic effects

‘Holographic’ Display for Miniature Projects!

I’ve been playing around with adding holographic displays into my builds and this uses the classic Pepper’s Ghost illusion, which looks seriously cool in person! It’s super simple and can even be done with any screen or phone if you don’t want to jump straight into electronics or coding.

Here’s a quick breakdown of how I did it. Feel free to ask questions or check the links for more details!

What I Used to create the effect:

– ST7735 1.8″ TFT Display Module
– ESP32 Development Board
– Dupont Jumper Wires
– 5V USB Power Pack
– 3D Printed Enclosure (STL files coming soon)
– Transparent acetate sheet or plastic film

The Process:

Step 1:
Followed this awesome tutorial → https://www.youtube.com/watch?v=GrNIfwUNaaw
Modified code from → bitbank2/AnimatedGIF GitHub

Step 2:
Wired the display to the ESP32 using this pinout (no soldering needed): VCC → 3v3, GND → GND, CS → 5, RESET → 4, AO → 2, SOA → 23, SCK → 18, LED → 3v3

Step 3:
Created an animated GIF (128x160px for my screen) and converted it using the tool suggested in tutorial.

Step 4:
Uploaded the modified code to the ESP32 using Arduino IDE. Note: You may need to tweak the User_Setup.h file based on your board.

Below is the main code I used

//TUTORIAL FROM https://www.youtube.com/watch?v=GrNIfwUNaaw
//CONVERT NEW GIF TO C (.h) WITH https://github.com/bitbank2/image_to_c
//Edit User_Setup.h for screen specific adjustments

// Edited by Saucermen Studios


//#define USE_DMA       // ESP32 ~1.25x single frame rendering performance boost for badgers.h
                        // Note: Do not use SPI DMA if reading GIF images from SPI SD card on same bus as TFT
  #define NORMAL_SPEED  // Comment out for rame rate for render speed test

// Load GIF library
#include <AnimatedGIF.h>
AnimatedGIF gif;

// Example AnimatedGIF library images
// #include "anim2.h"
 #include "anim3.h"
// #include "../test_images/badger.h"
// #include "vending1.h"
// #include "../test_images/homer_tiny.h"
// #include "../test_images/pattern.h"

                                // ESP32 40MHz SPI single frame rendering performance
                                // Note: no DMA performance gain on smaller images or transparent pixel GIFs
  #define GIF_IMAGE anim3   //  No DMA  63 fps, DMA:  71fps
//#define GIF_IMAGE ucHomer     //  No DMA 162 fps, DMA: 141 fps
//#define GIF_IMAGE homer_tiny  //  No DMA 564 fps, DMA: 481 fps
//#define GIF_IMAGE ucPattern   //  No DMA  90 fps, DMA:  78 fps

#include <SPI.h>
#include <TFT_eSPI.h>

// Color definitions
// #define BLACK   0x0000
// #define BLUE    0x001F
// #define RED     0xF800
// #define GREEN   0x07E0
// #define CYAN    0x07FF
// #define MAGENTA 0xF81F
// #define YELLOW  0xFFE0
// #define WHITE   0xFFFF


TFT_eSPI tft = TFT_eSPI();


#define imageWidth 128
#define imageHeight 160

void setup() {
  Serial.begin(115200);

  tft.begin();
#ifdef USE_DMA
  tft.initDMA();
#endif
  tft.setRotation(-1);
  tft.fillScreen(TFT_BLACK);
//  tft.invertDisplay(1);

gif.begin(BIG_ENDIAN_PIXELS); // COOKED mode
}


// Pin definitions
#define CS_PIN     5     // Chip select control pin
#define RST_PIN    4     // Reset pin (could connect to RST pin on board)
#define DC_PIN     2     // Data/Command control pin
#define SCLK_PIN   18    // Serial clock
#define MOSI__PIN   23    // Master Out Slave In (SPI data)
#define LED_PIN    -1    // Use -1 if backlight is always on or connected to 3.3v



#ifdef NORMAL_SPEED // Render at rate that is GIF controlled
void loop()
{
  // Put your main code here, to run repeatedly:
  if (gif.open((uint8_t *)GIF_IMAGE, sizeof(GIF_IMAGE), GIFDraw))
  {
    Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight());
    tft.startWrite(); // The TFT chip select is locked low
    while (gif.playFrame(true, NULL))
    {
      yield();
    }
    gif.close();
    tft.endWrite(); // Release TFT chip select for other SPI devices
  }
}
#else // Test maximum rendering speed
void loop()
{
  long lTime = micros();
  int iFrames = 0;

  if (gif.open((uint8_t *)GIF_IMAGE, sizeof(GIF_IMAGE), GIFDraw))
  {
    tft.startWrite(); // For DMA the TFT chip select is locked low
    while (gif.playFrame(false, NULL))
    {
      // Each loop renders one frame
      iFrames++;
      yield();
    }
    gif.close();
    tft.endWrite(); // Release TFT chip select for other SPI devices
    lTime = micros() - lTime;
    Serial.print(iFrames / (lTime / 1000000.0));
    Serial.println(" fps");
  }
}
#endif

Step 5:
3D printed the enclosure and slotted the screen in place.

Step 6:
Cut a piece of transparent film and mounted it at a 45° angle in front of the screen to get that floating hologram effect.