Skip to content

Instantly share code, notes, and snippets.

@phoenisx
Last active January 2, 2026 14:34
Show Gist options
  • Select an option

  • Save phoenisx/bdc603679915e3f90f25807067192079 to your computer and use it in GitHub Desktop.

Select an option

Save phoenisx/bdc603679915e3f90f25807067192079 to your computer and use it in GitHub Desktop.
Point Sphere
// frag shader
// Sphere without geometry
#define PI 3.1415926535897932384626433832795
varying mat4 vNormalMatrix;
#include includes/utils.glsl;
#include includes/lights.glsl;
void drawSphere(vec2 uv, float radius) {
// Point in 3D space, with `z` -> 0.0
vec3 p = vec3(uv, 0.0);
float signedDistance = sdSphere(p, radius);
float alpha = step(0., -1. * signedDistance);
vec3 color = vec3(0., 0.8, 1.0);
color *= alpha;
gl_FragColor = vec4(color, alpha);
}
void sphereImage() {
vec2 uv = centerUv(gl_PointCoord);
float radius = 1.;
drawSphere(uv, radius);
// Find normals for the sphere
// Re-calculate the depth/z of the "sphere" at any fragment position using the Pythagorean theorem:
// depth^2 + dist^2 = radius^2 (radius is 0.5)
// Also since radius is 1, 1^2 is 1, we aren't squaring it.
float z = sqrt(max(0., radius - dot(uv, uv)));
vec3 normal = normalize(vec3(uv.x, -uv.y, z));
normal = normalize(mat3(vNormalMatrix) * normal);
// Apply lights
// addAmbientLight(0.5);
addDirectionalLight(normal, vec3(0.5, 0.5, 1.), 1.);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
void main() {
sphereImage();
}
// utils.glsl
float sdSphere(vec3 p, float s) {
return length(p) - s;
}
vec2 centerUv(vec2 uv) {
// (gl_PointCoord - 0.5) - 2.0; // Both these are the same
// Creating a 2D space uniformly divided in range [-1.0, 1.0]
vec2 projected = 2. * uv - 1.;
return projected;
}
// vertex shader
uniform vec2 uResolution;
uniform float uSize;
varying mat4 vNormalMatrix;
void main() {
// The following transform matrix includes, Object self transforms and Camera transforms applied.
vec4 positionTransformed = modelViewMatrix * vec4(position, 1.0);
// 3d to 2d projection I guess
vec4 postionInClipSpace = projectionMatrix * positionTransformed;
// Since our Tile/Plane is already normalized, I don't need `uResolution`, like in thebookofshaders
gl_Position = postionInClipSpace;
// NOTE: Using particles to render sphere might not be the best solution, as some old OpenGL implementations had max limit
// on the particle size of 64 units. Check Fireworks lesson for details: https://threejs-journey.com/lessons/fireworks-shaders
gl_PointSize = uSize * uResolution.y;
// Scaling factor is always present on the diagonal of the matrix4
// projectionMatrix[1][1] points to the scale factor for Z.
// Helpful ref: https://www.youtube.com/watch?v=pLFXNmbDZk8&t=1s
gl_PointSize *= projectionMatrix[1][1] / - positionTransformed.z;
// This is very specific to our points, cause Points do not have any normals
// We are calculating normals in Fragment shader for which normalMatrix is required.
// Which we are using to calculate the Point Normal Matrix, including modelMatrix.
mat4 normalMatrix = inverse(modelViewMatrix);
// Varying
vNormalMatrix = modelMatrix * normalMatrix;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment