Drawing a 3D triangle on my OLED display only displays a single pixel

A while back I made a software renderer and I wanted to do the same on the arduino. It was even easier, as the Adafruit display library already has a display.fillTriangle function. I got the display working and I was able to render text and scrolling using the Adafruit example sketches in the Arduino IDE. Then I copied the code from my software renderer and then deleted the triangle rasterization code, as I didn't need it anymore. I put everything together, added the rendering code and when I ran the program, only one of the pixels rendered and that specific pixel wasn't even in the triangle's shape. I did some debugging and found out that the final value for the first and the second vertex is zero. That would mean that all of the positions for the triangle are zeroes. The same code was working on my computer and I am unsure what could've caused this. This is the triangle rendering code:

void drawtriangle(float pos1[3], float pos2[3], float pos3[3]){
   float v1[2];
   float v2[2];
   float v3[2];
   char buf[10];
   vertexshader(pos1, model, view, proj, v1);
   vertexshader(pos2, model, view, proj, v2);
   vertexshader(pos3, model, view, proj, v3);
   display.fillTriangle(v1[0], v1[1], v2[0], v2[1], v3[0], v3[1], WHITE);
   snprintf(buf, sizeof(buf), "%d", (int)v1[0]);
   Serial.write(buf);
   Serial.print(", ");
   snprintf(buf, sizeof(buf), "%d", (int)v2[0]);
   Serial.write(buf);
   Serial.write("\n");
}
void drawtrianglearray(float *positions, int size){
   float pos1[3];
   float pos2[3];
   float pos3[3];
   int i;
   for(i = 0; i < size / 9; i++){
      pos1[0] = positions[i * 9];
      pos1[1] = positions[i * 9 + 1];
      pos1[2] = positions[i * 9 + 2];
      pos2[0] = positions[i * 9 + 3];
      pos2[1] = positions[i * 9 + 4];
      pos2[2] = positions[i * 9 + 5];
      pos3[0] = positions[i * 9 + 6];
      pos3[1] = positions[i * 9 + 7];
      pos3[2] = positions[i * 9 + 8];
      drawtriangle(pos1, pos2, pos3);
   }
}

This is the vertex shader:

void vertexshader(float position[3], float model[16], float view[16], float proj[16], float out[2]){
   float int1[16];
   float int2[16];
   float int3[4];
   float int4[4];
   float int5[2];
   float int6[2];
   int3[0] = position[0];
   int3[1] = position[1];
   int3[2] = position[2];
   int3[3] = 1.0f;
   mat4_mul(view, model, int1);
   mat4_mul(proj, int1, int2);
   mat4_vec4_mul(int2, int3, int4);
   int5[0] = int4[0] / int4[3];
   int5[1] = int4[1] / int4[3];
   int6[0] = int5[0] * 0.5f + 0.5f;
   int6[1] = int5[1] * 0.5f + 0.5f;
   out[0] = int6[0] * width; 
   out[1] = (1.0f - int6[1]) * height;
}

And this is the main loop:

void loop() {
  display.clearDisplay();
  drawtrianglearray(vertices, sizeof(vertices)/sizeof(float));
  display.display();
  angle += 1.0f;
  mat4_rotate(angle, 1, model);
}

The mat4 functions are from a library I made. This is its code:

void mat2_mul(float a[4], float b[4], float *out){
   out[0] = a[0] * b[0] + a[1] * b[1];
   out[1] = a[0] * b[1] + a[1] * b[2];
   out[2] = a[2] * b[0] + a[3] * b[2];
   out[3] = a[2] * b[1] + a[3] * b[3];
}

void mat2_vec2_mul(float mat[4], float vec[2], float *out){
   out[0] = mat[0] * vec[0] + mat[1] * vec[1];
   out[1] = mat[2] * vec[0] + mat[3] * vec[1];
}

void mat4_mul(float l[16], float r[16], float *out){
   out[0]  = r[0] * l[0]  + r[4] * l[1]  + r[8]  * l[2]  + r[12] * l[3];
   out[1]  = r[1] * l[0]  + r[5] * l[1]  + r[9]  * l[2]  + r[13] * l[3];
   out[2]  = r[2] * l[0]  + r[6] * l[1]  + r[10] * l[2]  + r[14] * l[3];
   out[3]  = r[3] * l[0]  + r[7] * l[1]  + r[11] * l[2]  + r[15] * l[3];
   out[4]  = r[0] * l[4]  + r[4] * l[5]  + r[8]  * l[6]  + r[12] * l[7];
   out[5]  = r[1] * l[4]  + r[5] * l[5]  + r[9]  * l[6]  + r[13] * l[7];
   out[6]  = r[2] * l[4]  + r[6] * l[5]  + r[10] * l[6]  + r[14] * l[7];
   out[7]  = r[3] * l[4]  + r[7] * l[5]  + r[11] * l[6]  + r[15] * l[7];
   out[8]  = r[0] * l[8]  + r[4] * l[9]  + r[8]  * l[10] + r[12] * l[11];
   out[9]  = r[1] * l[8]  + r[5] * l[9]  + r[9]  * l[10] + r[13] * l[11];
   out[10] = r[2] * l[8]  + r[6] * l[9]  + r[10] * l[10] + r[14] * l[11];
   out[11] = r[3] * l[8]  + r[7] * l[9]  + r[11] * l[10] + r[15] * l[11];
   out[12] = r[0] * l[12] + r[4] * l[13] + r[8]  * l[14] + r[12] * l[15];
   out[13] = r[1] * l[12] + r[5] * l[13] + r[9]  * l[14] + r[13] * l[15];
   out[14] = r[2] * l[12] + r[6] * l[13] + r[10] * l[14] + r[14] * l[15];
   out[15] = r[3] * l[12] + r[7] * l[13] + r[11] * l[14] + r[15] * l[15];
}

void mat4_vec4_mul(float mat[16], float vec[4], float *out){
   out[0] = vec[0] * mat[0]  + vec[1] * mat[1]  + vec[2] * mat[2]  + vec[3] * mat[3];
   out[1] = vec[0] * mat[4]  + vec[1] * mat[5]  + vec[2] * mat[6]  + vec[3] * mat[7];
   out[2] = vec[0] * mat[8]  + vec[1] * mat[9]  + vec[2] * mat[10] + vec[3] * mat[11];
   out[3] = vec[0] * mat[12] + vec[1] * mat[13] + vec[2] * mat[14] + vec[3] * mat[15];
}

void mat4_rotate(float degrees, unsigned int axis, float *out){
   float radians = degrees * (22.0f / 7.0f) / 180.0f;
   if(axis == 0){
      out[0]  =  1.0f;
      out[1]  =  0.0f;
      out[2]  =  0.0f;
      out[3]  =  0.0f;
      out[4]  =  0.0f;
      out[5]  =  cosf(radians);
      out[6]  = -sinf(radians);
      out[7]  =  0.0f;
      out[8]  =  0.0f;
      out[9]  =  sinf(radians);
      out[10] =  cosf(radians);
      out[11] =  0.0f;
      out[12] =  0.0f;
      out[13] =  0.0f;
      out[14] =  0.0f;
      out[15] =  1.0f;
   }else if(axis == 1){
      out[0]  =  cosf(radians);
      out[1]  =  0.0f;
      out[2]  =  sinf(radians);
      out[3]  =  0.0f;
      out[4]  =  0.0f;
      out[5]  =  1.0f;
      out[6]  =  0.0f;
      out[7]  =  0.0f;
      out[8]  = -sinf(radians);
      out[9]  =  0.0f;
      out[10] =  cosf(radians);
      out[11] =  0.0f;
      out[12] =  0.0f;
      out[13] =  0.0f;
      out[14] =  0.0f;
      out[15] =  1.0f;
   }else if(axis == 2){
      out[0]  =  cosf(radians);
      out[1]  = -sinf(radians);
      out[2]  =  0.0f;
      out[3]  =  0.0f;
      out[4]  =  sinf(radians);
      out[5]  =  cosf(radians);
      out[6]  =  0.0f;
      out[7]  =  0.0f;
      out[8]  =  0.0f;
      out[9]  =  0.0f;
      out[10] =  1.0f;
      out[11] =  0.0f;
      out[12] =  0.0f;
      out[13] =  0.0f;
      out[14] =  0.0f;
      out[15] =  1.0f;
   }
}

I can't figure this out. Do you find a mistake in my code?

Edit: When I added the debug messages the program slowed down and was able to render one of the lines, but wasn't consistently rendering, as it was blinking a lot. And also I noticed that it might be rendering upside down. How much time does the library need to render it? I tried adding delays after the rendering of the triangle, but that makes it go back to only showing one pixel on the display.

What model of Arduino?

Put some Serial.print() statements at carefully selected places in the Arduino code, so you can see whether input, output and intermediate values make sense.

It is an Arduino Nano.

I did and all of the output and input variables of the vertex shader were set to 0. I found out that when I set the debug output to show before running drawtriangle() in the drawtrianglearray() function, a line of the triangle is rendered properly. I don't have any clue how this happens.

Put in MORE print statements to track that error down.

The error is probably in the code you did not post, in which case forum members can't help.

This is the full code:

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for SSD1306 display connected using software SPI (default case):
#define OLED_MOSI   9
#define OLED_CLK   10
#define OLED_DC    11
#define OLED_CS    12
#define OLED_RESET 13
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
  OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

const unsigned int width = 128;
const unsigned int height = 64;

float model[] = {1, 0, 0, 0,
                 0, 1, 0, 0,
                 0, 0, 1, 0,
                 0, 0, 0, 1};
const float view[] PROGMEM = {1, 0, 0, 0,
                              0, 1, 0, 0,
                              0, 0, 1, 2,
                              0, 0, 0, 1};
const float proj[] PROGMEM = {1, 0, 0, 0,
                              0, 1, 0, 0,
                              0, 0, 0, 1,
                              0, 0, 1, 0};
const float vertices[] PROGMEM = {
  -0.5f, -0.5f,  0.5f,
   0.0f,  0.5f,  0.5f,
   0.5f, -0.5f,  0.5f,
};
void mat2_mul(float a[4], float b[4], float *out){
   out[0] = a[0] * b[0] + a[1] * b[1];
   out[1] = a[0] * b[1] + a[1] * b[2];
   out[2] = a[2] * b[0] + a[3] * b[2];
   out[3] = a[2] * b[1] + a[3] * b[3];
}

void mat2_vec2_mul(float mat[4], float vec[2], float *out){
   out[0] = mat[0] * vec[0] + mat[1] * vec[1];
   out[1] = mat[2] * vec[0] + mat[3] * vec[1];
}

void mat4_mul(float l[16], float r[16], float *out){
   out[0]  = r[0] * l[0]  + r[4] * l[1]  + r[8]  * l[2]  + r[12] * l[3];
   out[1]  = r[1] * l[0]  + r[5] * l[1]  + r[9]  * l[2]  + r[13] * l[3];
   out[2]  = r[2] * l[0]  + r[6] * l[1]  + r[10] * l[2]  + r[14] * l[3];
   out[3]  = r[3] * l[0]  + r[7] * l[1]  + r[11] * l[2]  + r[15] * l[3];
   out[4]  = r[0] * l[4]  + r[4] * l[5]  + r[8]  * l[6]  + r[12] * l[7];
   out[5]  = r[1] * l[4]  + r[5] * l[5]  + r[9]  * l[6]  + r[13] * l[7];
   out[6]  = r[2] * l[4]  + r[6] * l[5]  + r[10] * l[6]  + r[14] * l[7];
   out[7]  = r[3] * l[4]  + r[7] * l[5]  + r[11] * l[6]  + r[15] * l[7];
   out[8]  = r[0] * l[8]  + r[4] * l[9]  + r[8]  * l[10] + r[12] * l[11];
   out[9]  = r[1] * l[8]  + r[5] * l[9]  + r[9]  * l[10] + r[13] * l[11];
   out[10] = r[2] * l[8]  + r[6] * l[9]  + r[10] * l[10] + r[14] * l[11];
   out[11] = r[3] * l[8]  + r[7] * l[9]  + r[11] * l[10] + r[15] * l[11];
   out[12] = r[0] * l[12] + r[4] * l[13] + r[8]  * l[14] + r[12] * l[15];
   out[13] = r[1] * l[12] + r[5] * l[13] + r[9]  * l[14] + r[13] * l[15];
   out[14] = r[2] * l[12] + r[6] * l[13] + r[10] * l[14] + r[14] * l[15];
   out[15] = r[3] * l[12] + r[7] * l[13] + r[11] * l[14] + r[15] * l[15];
}

void mat4_vec4_mul(float mat[16], float vec[4], float *out){
   out[0] = vec[0] * mat[0]  + vec[1] * mat[1]  + vec[2] * mat[2]  + vec[3] * mat[3];
   out[1] = vec[0] * mat[4]  + vec[1] * mat[5]  + vec[2] * mat[6]  + vec[3] * mat[7];
   out[2] = vec[0] * mat[8]  + vec[1] * mat[9]  + vec[2] * mat[10] + vec[3] * mat[11];
   out[3] = vec[0] * mat[12] + vec[1] * mat[13] + vec[2] * mat[14] + vec[3] * mat[15];
}

void mat4_rotate(float degrees, unsigned int axis, float *out){
   float radians = degrees * (22.0f / 7.0f) / 180.0f;
   if(axis == 0){
      out[0]  =  1.0f;
      out[1]  =  0.0f;
      out[2]  =  0.0f;
      out[3]  =  0.0f;
      out[4]  =  0.0f;
      out[5]  =  cosf(radians);
      out[6]  = -sinf(radians);
      out[7]  =  0.0f;
      out[8]  =  0.0f;
      out[9]  =  sinf(radians);
      out[10] =  cosf(radians);
      out[11] =  0.0f;
      out[12] =  0.0f;
      out[13] =  0.0f;
      out[14] =  0.0f;
      out[15] =  1.0f;
   }else if(axis == 1){
      out[0]  =  cosf(radians);
      out[1]  =  0.0f;
      out[2]  =  sinf(radians);
      out[3]  =  0.0f;
      out[4]  =  0.0f;
      out[5]  =  1.0f;
      out[6]  =  0.0f;
      out[7]  =  0.0f;
      out[8]  = -sinf(radians);
      out[9]  =  0.0f;
      out[10] =  cosf(radians);
      out[11] =  0.0f;
      out[12] =  0.0f;
      out[13] =  0.0f;
      out[14] =  0.0f;
      out[15] =  1.0f;
   }else if(axis == 2){
      out[0]  =  cosf(radians);
      out[1]  = -sinf(radians);
      out[2]  =  0.0f;
      out[3]  =  0.0f;
      out[4]  =  sinf(radians);
      out[5]  =  cosf(radians);
      out[6]  =  0.0f;
      out[7]  =  0.0f;
      out[8]  =  0.0f;
      out[9]  =  0.0f;
      out[10] =  1.0f;
      out[11] =  0.0f;
      out[12] =  0.0f;
      out[13] =  0.0f;
      out[14] =  0.0f;
      out[15] =  1.0f;
   }
}
void vertexshader(float position[3], float model[16], float view[16], float proj[16], float out[2]){
   float int1[16];
   float int2[16];
   float int3[4];
   float int4[4];
   float int5[2];
   float int6[2];
   char buf[10];
   int3[0] = position[0];
   int3[1] = position[1];
   int3[2] = position[2];
   int3[3] = 1.0f;
   mat4_mul(view, model, int1);
   mat4_mul(proj, int1, int2);
   mat4_vec4_mul(int2, int3, int4);
   int5[0] = int4[0] / int4[3];
   int5[1] = int4[1] / int4[3];
   int6[0] = int5[0] * 0.5f + 0.5f;
   int6[1] = int5[1] * 0.5f + 0.5f;
   out[0] = int6[0] * width; 
   out[1] = (1.0f - int6[1]) * height;
}
void drawtriangle(float pos1[3], float pos2[3], float pos3[3]){
   float v1[2];
   float v2[2];
   float v3[2];
   char buf[10];
   vertexshader(pos1, model, view, proj, v1);
   vertexshader(pos2, model, view, proj, v2);
   vertexshader(pos3, model, view, proj, v3);
   display.fillTriangle(v1[0], v1[1], v2[0], v2[1], v3[0], v3[1], WHITE);
}
void drawtrianglearray(float *positions, int size){
   float pos1[3];
   float pos2[3];
   float pos3[3];
   char buf[10];
   int i;
   for(i = 0; i < size / 9; i++){
      pos1[0] = positions[i * 9];
      pos1[1] = positions[i * 9 + 1];
      pos1[2] = positions[i * 9 + 2];
      pos2[0] = positions[i * 9 + 3];
      pos2[1] = positions[i * 9 + 4];
      pos2[2] = positions[i * 9 + 5];
      pos3[0] = positions[i * 9 + 6];
      pos3[1] = positions[i * 9 + 7];
      pos3[2] = positions[i * 9 + 8];
      drawtriangle(pos1, pos2, pos3);
   }
}
float angle = 0.0f;
void setup() {
  Serial.begin(9600);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }


  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay(); 
}

void loop() {
  display.clearDisplay();
  drawtrianglearray(vertices, sizeof(vertices)/sizeof(float));
  display.display();
  angle += 1.0f;
  mat4_rotate(angle, 1, model);
}

PROGMEM is at least one of the problems. Check the Arduino Reference pages to learn how to use this keyword and its associated functions correctly.

Thanks! I didn't do any reasearch before using it in my code, as before I got to this point I used my rasterization functions, but they filled the memory and I tried using PROGMEM, but after I started using Adafruit's GLX rasterizer, I left the PROGMEM where it was, as I didn't think it would be an issue.

Yeah, Nano has only 2KB ram memory. That's 2KB, not 2MB and certainly not 2GB. Your PC/laptop probably has more than 2GB, so Nano will have less than one millionth as much ram! So it might be an interesting challenge to get this working on Nano.

You might want to upgrade to something with a little more capability, such as a Nano 33, ESP8266/ESP32, Nano 33BLE....

I already got it to work with PROGMEM. I will probably look into getting a better arduino board though.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.