PROLOGUE

Welcome to part 0 of our software 3D renderer tutorial!

This part will cover the preparations. As mentioned, we’ll be using C++ and SDL2 and it’s expected that you’re familiar with these. When needed I’ll be quoting certain parts of code that I believe hold importance but won’t be copypasting sheets of it here so that you won’t get lost. Instead I’ll reference the source file in question, which you can then peruse at your leisure if necessary at some later point in time.


SDL2 project setup

First you will need to create simple SDL2 project. You can do it however you like, but personally I decided to kinda follow in One Lone Coder footsteps by creating base class with all necessary SDL2 setup done there, and then inherited custom class from it which overrides drawing and event handling methods. Such design also helps to streamline the process of creating any kind of side project for testing of various stuff (which I did extensively).

Since we’ll be doing low-level pixel stuff I strongly recommend to implement some sort of scaling variable, because otherwise it will be very hard to see if our drawing was done correctly or not. To do this we will be rendering into texture of certain size, that is determined by this scaling variable (called qualityReductionFactor in code), and later upscale it by blitting onto the screen. Here’s how it’s done in sw3d.cpp:

bool DrawWrapper::Init(uint16_t windowWidth,
                       uint16_t windowHeight,
                       uint16_t qualityReductionFactor)
{
  ...

  uint16_t canvasSize = std::min(windowWidth, windowHeight);
  _frameBufferSize = canvasSize / qualityReductionFactor;
  ...
  _framebuffer = SDL_CreateTexture(_renderer,
                                   SDL_PIXELFORMAT_RGBA32,
                                   SDL_TEXTUREACCESS_TARGET,
                                   _frameBufferSize,
                                   _frameBufferSize);
  ...
}

If we need “real” resolution we can always pass 1 to Init() method, but in the beginning and for debugging we will use values of about 4 or 6.

Your main loop is standard: poll all events and then call a function to draw, like so:

void DrawWrapper::Run(bool debugMode)
{
  SDL_Event evt;
  ...
  while (_running)
  {
    ...
    while (SDL_PollEvent(&evt))
    {
      HandleEvent(evt);
    }

    SDL_SetRenderTarget(_renderer, _framebuffer);
    SDL_RenderClear(_renderer);

    // Our 3D rendering will happen here.
    DrawToFrameBuffer();

    SDL_SetRenderTarget(_renderer, nullptr);
    SDL_RenderClear(_renderer);

    // Blit framebuffer texture to the screen.
    int ok = SDL_RenderCopy(_renderer, _framebuffer, nullptr, nullptr);
    if (ok < 0)
    {
      SDL_Log("%s", SDL_GetError());
    }

    // Perform additional drawing directly to the screen.
    // Can be useful for displaying overlayed text information, for example.
    DrawToScreen();

    SDL_RenderPresent(_renderer);
    ...
  }
}

And that’s everything we need to start coding our 3D stuff.


Next up: Line


Back to index page