OpenGL 3D Rendering with JUCE Made Simple in Three Steps

To render a geometry in just three steps with GitHub - floooh/sokol: minimal cross-platform standalone C headers

Step 1: Set Up the Pipeline

#pragma once

#include "sokol_gfx.h"

struct SimplePipeline {
    sg_pass pass;
    sg_pipeline pipeline;
    sg_pipeline pipeline_use_index;

    SimplePipeline(sg_primitive_type primitive_type = _SG_PRIMITIVETYPE_DEFAULT);
};



We specify vertex and fragment shaders here. The vertex shader sets up the vertex positions and colors, while the fragment shader processes the colors:

const char simple_quad_vs[] = R"(#version 300 es
    layout(location = 0) in vec4 in_position;
    layout(location = 1) in vec4 in_color;
    out vec4 color;
    void main() {
        gl_Position = in_position;
        color = in_color;
    })";

const char simple_quad_fs[] = R"(#version 300 es
    precision mediump float;
    in vec4 color;
    out vec4 FragColor;
    void main() {
        FragColor = color;
    })";

In SimplePipeline, we configure the layout of vertex attributes and then compile the shader:

SimplePipeline::SimplePipeline(sg_primitive_type primitive_type) {
    pipeline = sg_make_pipeline((sg_pipeline_desc) {
        .layout = {
            .attrs = {
                [0] = { .format = SG_VERTEXFORMAT_FLOAT3, .offset = 0 },
                [1] = { .format = SG_VERTEXFORMAT_FLOAT4, .offset = sizeof(float) * 3 }
            },
        },
        .primitive_type = primitive_type,
        .shader = sg_make_shader((sg_shader_desc) {
            .vs = { .source = simple_quad_vs },
            .fs = { .source = simple_quad_fs }
        }),
        .label = "simple-quad-pipeline",
    });
}

Step 2: Configure Buffer Bindings

We define the buffers required to hold the vertex and color data as well as the indices for drawing:

class SimpleQuad {
public:
    static SimpleQuad& Instance();
    const sg_bindings& GetBindings() const { return bindings; }
    const sg_bindings& GetBindings_use_index() const { return bindings_use_index; }
    int GetNumElements() const { return 3; }

private:
    SimpleQuad();
    sg_bindings bindings;
    sg_buffer vertex_buffer;
    sg_buffer index_buffer;
    sg_bindings bindings_use_index;
};


SimpleQuad::SimpleQuad() {
    float vertices[] = {
        0.0f,  0.5f, 0.5f,  1.0f, 0.0f, 0.0f, 1.0f,
        0.5f, -0.5f, 0.5f,  0.0f, 1.0f, 0.0f, 1.0f,
       -0.5f, -0.5f, 0.5f,  0.0f, 0.0f, 1.0f, 1.0f
    };
    vertex_buffer = sg_make_buffer({ .data = SG_RANGE(vertices), .label = "quad-vertices" });
    bindings = (sg_bindings){ .vertex_buffers[0] = vertex_buffer };

    uint16_t indices[] = { 0, 1, 2 };
    index_buffer = sg_make_buffer({ .type = SG_BUFFERTYPE_INDEXBUFFER, .data = SG_RANGE(indices) });
    bindings_use_index = (sg_bindings){ .vertex_buffers[0] = vertex_buffer, .index_buffer = index_buffer };
}


Step 3: Draw the Shape

With the pipeline and buffers set up, all that’s left is to render the triangle. In the render loop, we bind the pipeline and then draw the shape:

            {
                sg_apply_pipeline(_quard_pipeline->pipeline);
                sg_apply_bindings(SimpleQuad::Instance().GetBindings());
                sg_draw(0, SimpleQuad::Instance().GetNumElements(), 1);
            }

The final result

(GitHub - iomeone/jucesokoltest at step11_triangle_2)

Need help:

    this->openGLContext.setComponentPaintingEnabled(false);

    addAndMakeVisible(button);
    button.setButtonText("Click Me");

If I want to show the button, then I must setComponentPaintingEnabled to true, but when I set it to true, then the 3D scene would not show.
How to get around this issue?
I want to use gui to configure lots of 3d parameters in the future!

If you want to render a circle .Just generate the circle data, and config the bindings

Step 1

#include "CircleShape.h"
#include <vector>
#include <cmath>

#define PI 3.14159265358979323846

CircleShape& CircleShape::Instance() {
    static CircleShape instance;
    return instance;
}

CircleShape::CircleShape() {
    // Number of points for the circle
    const int N = 100;  // Adjust for smoothness

    std::vector<float> vertices;
    vertices.reserve(N * 7); // Each vertex has 7 floats (position + color)

    for (int i = 0; i < N; ++i) {
        float t = (float)i / (float)(N - 1) * 2.0f * PI;

        // Calculate position
        float x = cosf(t);
        float y = sinf(t);
        float z = 0.0f;

        // Calculate color (optional, can be customized)
        float r = (sinf(t) + 1.0f) * 0.5f;
        float g = (cosf(t) + 1.0f) * 0.5f;
        float b = 0.5f;
        float a = 1.0f;

        // Add position
        vertices.push_back(x);
        vertices.push_back(y);
        vertices.push_back(z);

        // Add color
        vertices.push_back(r);
        vertices.push_back(g);
        vertices.push_back(b);
        vertices.push_back(a);
    }

    vertex_count = N;

    // Create the vertex buffer
    sg_buffer_desc buffer_desc = {
        .data = {
            .ptr = vertices.data(),
            .size = vertices.size() * sizeof(float)
        },
        .label = "circle-vertices",
    };

    vertex_buffer = sg_make_buffer(&buffer_desc);

    bindings = {
        .vertex_buffers[0] = vertex_buffer,
    };
}

CircleShape::~CircleShape() {
    sg_destroy_buffer(vertex_buffer);
}


Step 2: Draw it

            {
                sg_apply_pipeline(_quard_pipeline_line_strip->pipeline);
                sg_apply_bindings(CircleShape::Instance().GetBindings());
                sg_draw(0, CircleShape::Instance().GetNumElements(), 1);
            }

We use the old pipeline, because the pipeline hasn’t change !

Code at:
(jucesokoltest/Source/CircleShape.cpp at step11_triangle_2 · iomeone/jucesokoltest · GitHub)

Final result:

We now want to render a rose curve, the formular for the rose curve is :

x=cos(t)sin(kt)
y=sin(t)sin(kt)

Just two steps:

Step 1: configure the binding


RoseCurve::RoseCurve() {
    // Number of points for the rose curve
    const int N = 100;

    std::vector<float> vertices;
    vertices.reserve(N * 7); // Each vertex has 7 floats (position + color)

    for (int i = 0; i < N; ++i) {
        float t = (float)i / (float)(N - 1) * 2.0f * PI;

        // Calculate position
        float x = cosf(t) * sinf(4.0f * t);
        float y = sinf(t) * sinf(4.0f * t);
        float z = 0.0f;

        // Calculate color (optional, can be customized)
        float r = (sinf(t) + 1.0f) * 0.5f;
        float g = (cosf(t) + 1.0f) * 0.5f;
        float b = 0.5f;
        float a = 1.0f;

        // Add position
        vertices.push_back(x);
        vertices.push_back(y);
        vertices.push_back(z);

        // Add color
        vertices.push_back(r);
        vertices.push_back(g);
        vertices.push_back(b);
        vertices.push_back(a);
    }

    vertex_count = N;

    // Create the vertex buffer
    sg_buffer_desc buffer_desc = {
        .data = {
            .ptr = vertices.data(),
            .size = vertices.size() * sizeof(float)
        },
        .label = "rose-curve-vertices",
    };

    vertex_buffer = sg_make_buffer(&buffer_desc);

    bindings = {
        .vertex_buffers[0] = vertex_buffer,
    };
}

Step 2

Draw it

            {
                sg_apply_pipeline(_quard_pipeline_line_strip->pipeline);
                sg_apply_bindings(RoseCurve::Instance().GetBindings());
                sg_draw(0, RoseCurve::Instance().GetNumElements(), 1);
            }

Code at:
https://github.com/iomeone/jucesokoltest/blob/step11_triangle_2/Source/RoseCurve.cpp

final result:

Draw a Lemniscate

Step 1, configure the binding


SimpleLemniscate::SimpleLemniscate() {
 
    const int u_steps = 50; 
    const int v_steps = 50; 

    std::vector<float> vertices;
    std::vector<uint16_t> indices;

 
    for (int i = 0; i <= u_steps; ++i) {
        float u = M_PI * i / (float)u_steps; // 0 <= u <= pi
        for (int j = 0; j <= v_steps; ++j) {
            float v = M_PI * j / (float)v_steps; // 0 <= v <= pi

            float cos_v = cosf(v);
            float sin_2u = sinf(2 * u);
            float sqrt_sin_2u = sqrtf(fabsf(sin_2u));

            float cos_u = cosf(u);
            float sin_u = sinf(u);
            float x = cos_v * sqrt_sin_2u * cos_u;
            float y = cos_v * sqrt_sin_2u * sin_u;

            float tan_v = tanf(v);
            float tan_v_squared = tan_v * tan_v;

            float z = x * x - y * y + 2 * x * y * tan_v_squared;
   

         
            float r = (x + 1.0f) * 0.5f; //[-1,1] to [0,1]
            float g = (y + 1.0f) * 0.5f;
            float b = (z + 1.0f) * 0.5f;
            float a = 1.0f;

           
            vertices.push_back(x);
            vertices.push_back(y);
            vertices.push_back(z);
            vertices.push_back(r);
            vertices.push_back(g);
            vertices.push_back(b);
            vertices.push_back(a);
        }
    }

 
    for (int i = 0; i < u_steps; ++i) {
        for (int j = 0; j < v_steps; ++j) {
            uint16_t index0 = i * (v_steps + 1) + j;
            uint16_t index1 = index0 + 1;
            uint16_t index2 = index0 + (v_steps + 1);
            uint16_t index3 = index2 + 1;

            indices.push_back(index0);
            indices.push_back(index2);
            indices.push_back(index1);

          
            indices.push_back(index1);
            indices.push_back(index2);
            indices.push_back(index3);
        }
    }

    num_elements = static_cast<int>(indices.size());

    vertex_buffer = sg_make_buffer({
        .type = SG_BUFFERTYPE_VERTEXBUFFER,
        .data = {
            .ptr = vertices.data(),
            .size = vertices.size() * sizeof(float)
        },
        .label = "lemniscate-vertices",
        });

    index_buffer = sg_make_buffer({
        .type = SG_BUFFERTYPE_INDEXBUFFER,
        .data = {
            .ptr = indices.data(),
            .size = indices.size() * sizeof(uint16_t)
        },
        .label = "lemniscate-indices",
        });

    bindings = (sg_bindings){
        .vertex_buffers[0] = vertex_buffer,
        .index_buffer = index_buffer,
    };
}

Step 2, Daw it

            {
                sg_apply_pipeline(_quard_pipeline_line_strip->pipeline_use_index);
                sg_apply_bindings(SimpleLemniscate::Instance().GetBindings());
                sg_draw(0, SimpleLemniscate::Instance().GetNumElements(), 1);
            }

code at:
https://github.com/iomeone/jucesokoltest/blob/step11_triangle_2/Source/simple_lemniscate.cpp

Issues with JUCE and sokol Rendering

Pimpl (OpenGLContext& c, const int w, const int h,
       const bool wantsDepthBuffer, const bool wantsStencilBuffer)
    : context (c), width (w), height (h),
      textureID (0), frameBufferID (0), depthOrStencilBuffer (0),
      hasDepthBuffer (false), hasStencilBuffer (false)
{
    // Code to initialize framebuffer
    context.extensions.glGenFramebuffers(1, &frameBufferID);
    bind();

    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    // This line is the issue
    context.extensions.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0);

    if (wantsDepthBuffer || wantsStencilBuffer)
    {
        // Depth/stencil buffer setup code
    }

    unbind();
}

When I comment out both of these lines:

context.extensions.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0);
unbind();

The sokol-rendered content shows up, but the JUCE UI does not render. When both lines are uncommented, the JUCE UI is displayed, but the sokol content is no longer visible.

How can I make sokol and JUCE 2D UI compatible?

I finally figured out the solution. By calling sg_reset_state_cache() before performing sokol rendering, I can correctly render both the JUCE GUI and sokol’s 3D content simultaneously.