# 3D pixel-sized point rendering ideas

**URL:** <https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909>\
**Category:** Using Cinder\
**Created:** [January 17, 2022, 5:49pm UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909 "2022-01-17T17:49:59Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![Gazoo](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/gazoo/32/61_2.png) [@Gazoo](https://discourse.libcinder.org/u/Gazoo)\
**Post date:** [January 17, 2022, 5:49pm UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909/1 "2022-01-17T17:49:59Z")

</div>

Hey Embers,

I’ve got a bit of an odd question. I’ve been playing around trying to render something that’s very reminiscent of a classic starfield from a 90s amiga/pc crack intro. Albeit with some modern adjustments into the new era, ala. adding tiny galaxies filled with stars etc.

One thing I’ve been having trouble with though, is rendering 3d points on-screen as pixels, no matter the distance to the camera. I’m wondering if anyone has any experience or suggestions in leveraging the built in functions in Cinder to achieve this?

So far I’ve been using `drawSolidCircle` with a very small radius which occasionally looks fine, but at other times looks like a tiny triangle as opposed to a pixel.

If possible it’d be cool if the approach also allowed for some custom change to the pixel size, a 2x2 pixel perhaps.

It’d be ideal if the approach didn’t have to escalate to using shaders in terms of complexity, but I’ve not really found anything simple that allows these pixel-sized points to exist in 3d space and not occasionally disappear or fail to occlude behind other geometry.

Thanks in advance,  
Gazoo

---

<div class="post-metadata">

**Author:** ![lithium](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/lithium/32/38_2.png) [@lithium](https://discourse.libcinder.org/u/lithium)\
**Post date:** [January 17, 2022, 6:02pm UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909/2 "2022-01-17T18:02:12Z")

</div>

The default behaviour of `GL_POINTS` will do this. There’s also room to get fancy with `GL_VERTEX_PROGRAM_POINT_SIZE` and textured points using `gl_PointCoord`, should you need it. This will be considerably faster than `drawSolidCircle` too.

```cpp
#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/CameraUi.h"
#include "cinder/Rand.h"
#include "cinder/gl/gl.h"

using namespace ci;
using namespace ci::app;
using namespace std;

class PointsApp : public ci::app::App
{
public:
    
    void setup ( ) override
    {
        _cam = CameraPersp ( getWindowWidth(), getWindowHeight(), 60.0f, 0.1f, 1000.0f );
        _cam.lookAt( vec3 ( 0, 0, 0.5 ), vec3 ( 0 ) );
        
        _camUi = CameraUi ( &_cam, getWindow() );
        _camUi.setMinimumPivotDistance( 0.0f );
        
        gl::VertBatch batch { GL_POINTS };
        for ( int i = 0; i < 1000; i++ )
        {
            float x = randFloat(-1, 1);
            float y = randFloat(-1, 1);
            float z = randFloat(-1, 1);
            
            batch.vertex( vec3 ( x, y, z ) );
        }
        
        auto shader = gl::getStockShader( gl::ShaderDef().color() );
        _batch = gl::Batch::create ( batch, shader );
        
        gl::pointSize(4);
    }
    
    void draw ( ) override
    {
        gl::clear( Colorf::gray(0.1f));
        gl::setMatrices(_cam);
        
        gl::ScopedColor color { Colorf::white() };
        _batch->draw();

    }
    
    
    gl::BatchRef _batch;
    CameraPersp _cam;
    CameraUi _camUi;
};

CINDER_APP ( PointsApp, RendererGl );

```

---

<div class="post-metadata">

**Author:** ![Gazoo](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/gazoo/32/61_2.png) [@Gazoo](https://discourse.libcinder.org/u/Gazoo)\
**Post date:** [January 17, 2022, 8:10pm UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909/3 "2022-01-17T20:10:03Z")

</div>

Much obliged @lithium . Did the ol’ copy paste and it looks solid! Really appreciate the idea and code.

As a side note, I wish there was a simpler way of coordinating objects in the CPU with visual graphics on the GPU other than just continuously sync the data, e.g. move independent star objects on the CPU based on some model, update GPU values, render, rinse, repeat, etc.

I also had an interest in possibly rendering the points in a roundish manner and found [this post](https://community.khronos.org/t/rounded-and-square-points/77249), talking about `GL_POINT_SMOOTH`. But it appears this enum isn’t naturally part of glfw in Cinder 0.9.2?

This [other post](http://philguo.com/blog/2016/point-cloud-dof/), has a potential work around for anyone wondering, but I assume just copying the enum value from the upcoming `glad.h` header should work too…?

Update: Hmmm no - doesn’t appear like just using that define will yield rounded points using the fixed pipeline.

Gazoo

---

<div class="post-metadata">

**Author:** ![lithium](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/lithium/32/38_2.png) [@lithium](https://discourse.libcinder.org/u/lithium)\
**Post date:** [January 18, 2022, 12:08am UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909/4 "2022-01-18T00:08:47Z")

</div>

> [@Gazoo](#):
>
> I also had an interest in possibly rendering the points in a roundish manner and found [this post](https://community.khronos.org/t/rounded-and-square-points/77249), talking about `GL_POINT_SMOOTH` . But it appears this enum isn’t naturally part of glfw in Cinder 0.9.2?

This is where the aforementioned point sprites come in.

```cpp
#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/CameraUi.h"
#include "cinder/Rand.h"
#include "cinder/gl/gl.h"

using namespace ci;
using namespace ci::app;
using namespace std;

class PointsApp : public ci::app::App
{
public:
    
    void setup ( ) override
    {
        auto vert = gl::env()->generateVertexShader( gl::ShaderDef().color() );
        //auto frag = gl::env()->generateFragmentShader( gl::ShaderDef().color().texture() );

        auto frag = CI_GLSL(150,
                            out vec4 oColor;
                            uniform sampler2D uTex0;
                            in vec4 Color;
                            void main( void )
                            {
                                oColor = vec4( 1 ) * texture( uTex0, gl_PointCoord.st ) * Color;
                            });
        
        auto shader = gl::GlslProg::create ( vert, frag );
        
        
        auto fmt = gl::Texture::Format().mipmap().minFilter(GL_LINEAR_MIPMAP_LINEAR).magFilter(GL_LINEAR);
        _texture = gl::Texture::create ( loadImage( loadUrl ( "https://d1yjxggot69855.cloudfront.net/images/b/ba/Cloud-particle.png" ) ), fmt );
        
        _cam = CameraPersp ( getWindowWidth(), getWindowHeight(), 60.0f, 0.1f, 1000.0f );
        _cam.lookAt( vec3 ( 0, 0, 0.5 ), vec3 ( 0 ) );
        
        _camUi = CameraUi ( &_cam, getWindow() );
        _camUi.setMinimumPivotDistance( 0.0f );
        
        gl::VertBatch batch { GL_POINTS };
        for ( int i = 0; i < 1000; i++ )
        {
            float x = randFloat(-1, 1);
            float y = randFloat(-1, 1);
            float z = randFloat(-1, 1);
            
            batch.vertex( vec3 ( x, y, z ) );
        }
        
        
        _batch = gl::Batch::create ( batch, shader );
        
        gl::pointSize(32);
    }
    
    void draw ( ) override
    {
        gl::clear( Colorf::gray(0.1f));
        gl::setMatrices(_cam);
        
        gl::ScopedBlendAlpha blend;
        gl::ScopedColor color { Colorf::white() };
        gl::ScopedTextureBind tex0 { _texture, 0 };
        
        _batch->draw();

    }
        
    gl::TextureRef _texture;
    gl::BatchRef _batch;
    CameraPersp _cam;
    CameraUi _camUi;
};

CINDER_APP ( PointsApp, RendererGl );

```

> [@Gazoo](#):
>
> As a side note, I wish there was a simpler way of coordinating objects in the CPU with visual graphics on the GPU other than just continuously sync the data, e.g. move independent star objects on the CPU based on some model, update GPU values, render, rinse, repeat, etc.

You can do a lot more GPU side these days with compute shaders and SSBOs etc, but really you’re just trading one series of problems for another. It’s just the nature of the beast really, and cinder is already shielding you from the worst of it.

---

<div class="post-metadata">

**Author:** ![Gazoo](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/gazoo/32/61_2.png) [@Gazoo](https://discourse.libcinder.org/u/Gazoo)\
**Post date:** [January 18, 2022, 3:00pm UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909/5 "2022-01-18T15:00:44Z")

</div>

Again - so awesome @lithium with a working example. Already ran it to have a look. I want to look into how to make the circles non-soft, and you’ve provided a great starting point.

You’re also right that ultimately, it’s a lot of replacing one issue with another. It’s also just a design preference as to whether one prefers arrays of objects or objects of arrays.

I find the former mentally simpler to manage so unless performance requires it, I lean towards that.

And I also 100% agree - Cinder is a great ‘annoyance’ shield. Makes so much, so much easier!

Gazoo

---

<div class="post-metadata">

**Author:** ![Gazoo](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/gazoo/32/61_2.png) [@Gazoo](https://discourse.libcinder.org/u/Gazoo)\
**Post date:** [January 22, 2022, 2:45pm UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909/6 "2022-01-22T14:45:17Z")

</div>

![image](https://canada1.discourse-cdn.com/flex030/uploads/libcinder/original/1X/998d22bd66f4155aabdaa2dea9bd91f04e54f94a.png)

One thing I’ve yet been able to find a simple solution to is achieving uniform pixel sizes with point size. As evident by that image, all my point sprites are of varying size. I believe it’s the result of the rendering pipeline having to render the point sprite at an uneven screen coordinate or perhaps it’s uneven lookups into the texture? It’s unclear to me what I need to do to ensure a uniform square pixel size.

Open to ideas!

I acknowledge doing so will make the rendering slightly more inaccurate position wise.

Gazoo

---

<div class="post-metadata">

**Author:** ![lithium](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/lithium/32/38_2.png) [@lithium](https://discourse.libcinder.org/u/lithium)\
**Post date:** [January 23, 2022, 2:08am UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909/7 "2022-01-23T02:08:38Z")

</div>

These look ok to me, perhaps it’s more pronounced when in motion?

Anyway, since you’re only doing circles, perhaps a procedural approach is better than a textured one in your case. If you replace the fragment shader with this, it will draw circles for you. You can tweak the `uRadius` and `uBlur` uniforms to adjust the softness of the circles.

```cpp
auto frag = CI_GLSL(150,

            float circle ( in vec2 st, in float radius, float blur)
            {
                vec2 dist = st - vec2 ( 0.5f );
                return 1.0f - smoothstep ( radius - ( radius * blur ),
                                           radius + ( radius * blur ),
                                           dot ( dist, dist ) * 4.0 );
            }
            
            out vec4 oColor;
            in vec4 Color;

            uniform float uBlur = 0.01;
            uniform float uRadius = 0.9;

            void main( void )
            {
                float c = circle ( gl_PointCoord.st, uRadius, uBlur );
                oColor = Color * vec4(1, 1, 1, c);
            });

```

---

<div class="post-metadata">

**Author:** ![Gazoo](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/gazoo/32/61_2.png) [@Gazoo](https://discourse.libcinder.org/u/Gazoo)\
**Post date:** [January 23, 2022, 9:22am UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909/8 "2022-01-23T09:22:19Z")

</div>

More amazingness from you @lithium. Your provided fragment shader effectively minimizes the issue to the point of it being invisible - so I’m leaning towards adopting it.

I’m keen to still investigate the issue I’m having as I could see it being an issue I might run into again in the future. I’m pretty sure we’re on the same page, but given the sizable help you’ve provided @lithium , I thought I’d still detail the issue I had.

![CinderChat](https://canada1.discourse-cdn.com/flex030/uploads/libcinder/original/1X/8d0c497a76b8c4ff8d9469cb7f63b44f0db52b38.png)  
Above are three concatenated images (from left to right):

- A slice of the previous screenshot
- A zoomed version of that slice, where I’ve highlighted nice ‘uniform square’ using stars with green rings, and red arrows to point out some of the misshapen stars
- The used texture

The issue is definitely more pronounced while in movement as you note lithium, but even when static, the non-uniform sizing of the texture is pretty visible. Given that lots of two dimensional games don’t have this issue, I’m sure there’s a way to side-step in the pipeline. I’ll keep investigating.

One last question to you @lithium - Would you recommend rendering these point sprites from back to front? I think it’s the only way to properly handle overlapping transparency. Otherwise you can get this delicious outcome:

![Animation4](https://canada1.discourse-cdn.com/flex030/uploads/libcinder/original/1X/467a3314f6e4b701a5050b1a4aaf50bfd3672edb.gif)

A semi-helpful intermediate solution is to append the following:

```auto
if (oColor.a <= 0.0) {
	discard;
}

```

to the fragment shader - but that doesn’t resolve the transparent pixels.

---

<div class="post-metadata">

**Author:** ![lithium](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/lithium/32/38_2.png) [@lithium](https://discourse.libcinder.org/u/lithium)\
**Post date:** [January 23, 2022, 9:57am UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909/9 "2022-01-23T09:57:17Z")

</div>

1. What filtering are you using on your particle texture, and does the texture vary in size from what `glPointSize` you’re using? Ideally they’d be 1 to 1, but failing that, you’ll probably get best results with `gl::Texture::Format().mipmap().minFilter(GL_LINEAR_MIPMAP_LINEAR).magFilter(GL_LINEAR);`

1. If you’re deliberately going for a pixelated look with `GL_NEAREST`, there’s some filtering techniques you can implement in your shader to mitigate the shimmering. See [here](https://www.shadertoy.com/view/ltBGWc) or [here](https://www.shadertoy.com/view/MllBWf) for sample implementations.

2. What is your gl state when you’re drawing? With depth read/write on and alpha blending I can’t reproduce what you’re seeing.  
 ![ezgif-7-818be3b3f4](https://canada1.discourse-cdn.com/flex030/uploads/libcinder/original/1X/193615b6fa8a65a49697619e54e77c4878de335b.gif)

I know that point sprites differ in the implementations across vendors, amd and nvidia handle them way differently so this may be an nvidia specific problem. In which case you can either sort, `discard` in the shader, or if your background allows it, draw with additive blending (`gl::ScopedBlendAdditive blend;` above your draw)

A

---

<div class="post-metadata">

**Author:** ![Gazoo](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/gazoo/32/61_2.png) [@Gazoo](https://discourse.libcinder.org/u/Gazoo)\
**Post date:** [January 23, 2022, 10:17am UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909/10 "2022-01-23T10:17:11Z")

</div>

1. That’s a great idea - I’ll keep that one in mind for sure.

2. Those are some amazing shadertoys. Even just learning of the term ‘shimmering’, is a tremendous help - very much appreciated @lithium . I would say they’ve certainly properly addressed the issue I had.

3. My draw() call is the following:

```auto
gl::clear( Colorf::gray( 0.1f ) );
gl::setMatrices( _cam );

gl::ScopedBlendAlpha blend;
gl::ScopedColor color{ Colorf::white() };
gl::ScopedTextureBind tex0{ _texture, 0 };

gl::ScopedDepth depth( true );

_batch->draw();

```

Replacing the existing blend with `gl::ScopedBlendAdditive blend2;` has no impact on the issue. Only disabling the depth buffer addresses the issue which, in my case, happens to be an acceptable approach to allow for some nicer blurred edges.

It’d be lovely if there was a drawing call that actually allowed for the depth buffer to be active without the issue presenting itself.

I’m assuming you’re on ATi/AMD hardware then?

---

<div class="post-metadata">

**Author:** ![lithium](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/lithium/32/38_2.png) [@lithium](https://discourse.libcinder.org/u/lithium)\
**Post date:** [January 23, 2022, 10:37am UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909/11 "2022-01-23T10:37:51Z")

</div>

~~I was on my macbook so that has an AMD radeon pro in it, but I just tried it on my windows desktop which has an RTX 3070 and it looked the same with no transparency issues, so something else may be afoot. It’s dubious that additive blending had no effect.~~

Sorry i missed that you had depth enabled, ignore this.

---

<div class="post-metadata">

**Author:** ![Gazoo](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/gazoo/32/61_2.png) [@Gazoo](https://discourse.libcinder.org/u/Gazoo)\
**Post date:** [January 23, 2022, 10:41am UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909/12 "2022-01-23T10:41:57Z")

</div>

Just to ensure I had not naughtily injected something I wasn’t aware of, I recopied your latest ‘full program’ code, injected the newest fragment shader, enabled depth, disabled the texture bind call and get this:

![image](https://canada1.discourse-cdn.com/flex030/uploads/libcinder/original/1X/0291d7594c81914bc732dd3856d92769c5b9ae2d.png)

I’m on a 2080 Super.

---

<div class="post-metadata">

**Author:** ![lithium](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/lithium/32/38_2.png) [@lithium](https://discourse.libcinder.org/u/lithium)\
**Post date:** [January 23, 2022, 10:48am UTC](https://discourse.libcinder.org/t/3d-pixel-sized-point-rendering-ideas/1909/13 "2022-01-23T10:48:25Z")

</div>

Should be able to get away with something like this to finagle the right draw state. First block is your existing fully depth read/write scene, the second block ( the point sprites ) respects the existing state of the depth buffer but doesn’t contribute to it, leaving you with (hopefully) the correct output.

```cpp
void draw ( ) override
{
    gl::clear( Colorf::gray(0.2f));
    gl::setMatrices(_cam);
    
    {
        // Normal 3D Scene
        gl::ScopedDepth depth { true };
        
        gl::ScopedGlslProg shader { gl::getStockShader ( gl::ShaderDef().lambert() ) };
        gl::drawCube( vec3(0), vec3(0.25));
    }
    
    {
        // Point Sprites
        gl::ScopedDepthTest depthTest { true };
        gl::ScopedDepthWrite depthWrite { false };
        
        _batch->getGlslProg()->uniform( "uRadius", _radius );
        _batch->getGlslProg()->uniform( "uBlur", _blur );
        
        gl::ScopedBlendAlpha blend;
        gl::ScopedColor color { Colorf::white() };
            
        _batch->draw();
    }
}

```
