No description
Find a file
2024-10-26 19:17:56 +02:00
build init with verions 0.7 2024-10-26 19:17:56 +02:00
res init with verions 0.7 2024-10-26 19:17:56 +02:00
src init with verions 0.7 2024-10-26 19:17:56 +02:00
.gitignore init with verions 0.7 2024-10-26 19:17:56 +02:00
CMakeLists.txt init with verions 0.7 2024-10-26 19:17:56 +02:00
COMPILE.md init with verions 0.7 2024-10-26 19:17:56 +02:00
README.md init with verions 0.7 2024-10-26 19:17:56 +02:00

Shaderparty

This Tool can be used to develop shaders in a convenien way.

Functions

the functions of this program needs to be documented. OwO

Texture and Colorspace

Shaderparty can load one Texture, which can be accessed via sampler (look chapter Hidden Shader Parts). The File must be a .png-File. This program is not aware of any colorspaces. So the values of an texture must either be converted in the shader according the colorspace the file is saved, or it is saved in linear colorspace.

Also the output of the shader is not converted into any colorspace. The conversation to a colorspace must be handled manually inside the shader. To convert to sRGB the following code can be used:

fragColor.r = fragColor.r <= 0.0031308 ? 12.92*fragColor.r : (1.055* pow(fragColor.r, 1.0/2.4)) - 0.055;
fragColor.g = fragColor.g <= 0.0031308 ? 12.92*fragColor.g : (1.055* pow(fragColor.g, 1.0/2.4)) - 0.055;
fragColor.b = fragColor.b <= 0.0031308 ? 12.92*fragColor.b : (1.055* pow(fragColor.b, 1.0/2.4)) - 0.055;

Hidden Shader Parts

Some parts of the shader are hardcoded on top of the one, that can be written. There is at least the following to put on the top of the shader, when the written shader should be exported:

#version 450 core

layout (location =  0) uniform float iTime;
layout (location =  1) uniform vec2 iResolution;
layout (location =  2) uniform vec4 iMouse;
layout (location =  3) uniform vec4 iTestVec[5];
layout (location =  8) uniform vec4 iTestColor[5];

layout (binding = 0) uniform sampler2D tex;

out vec4 fragColor;

Additionally the hg_sdf.frag from mercury is included. If some function or definition from this file is used, it must be included in the final shader as well. This file contains following functions and definitions:

Helper Funtions/Macros

#define PI
#define TAU
#define PHI
#define saturate(x)

// Sign function that doesn't return 0
vec[1|2]   sgn       (vec[1|2] v)
vec[1|2|3] square    (vec[1|2|3] x)
float      lengthSqr (vec3 x)
// Maximum/minumum elements of a vector
float vmax  (vec[2|3|4] v)
float vmin  (vec[2|3|4] v)

Primitive Distance Functions

Conventions:

Everything that is a distance function is called fSomething. The first argument is always a point in 2 or 3-space called p. Unless otherwise noted, (if the object has an intrinsic "up" side or direction) the y axis is "up" and the object is centered at the origin.

float fSphere(vec3 p, float r)

Plane with normal n (n is normalized) at some distance from the origin

float fPlane(vec3 p, vec3 n, float distanceFromOrigin)

Cheap Box: distance to corners is overestimated

float fBoxCheap(vec3 p, vec3 b)

Box: correct distance to corners

float fBox(vec3 p, vec3 b)

Same as above, but in two dimensions (an endless box)

float fBox2Cheap(vec2 p, vec2 b)
float fBox2(vec2 p, vec2 b)

Endless "corner"

float fCorner (vec2 p)

Blobby ball object. You've probably seen it somewhere. This is not a correct distance bound, beware.

float fBlob(vec3 p)

Cylinder standing upright on the xz plane

float fCylinder(vec3 p, float r, float height)

Capsule: A Cylinder with round caps on both sides

float fCapsule(vec3 p, float r, float c)

Distance to line segment between a and b, used for fCapsule() version 2below

float fLineSegment(vec3 p, vec3 a, vec3 b)

Capsule version 2: between two end points a and b with radius r

float fCapsule(vec3 p, vec3 a, vec3 b, float r)

Torus in the XZ-plane

float fTorus(vec3 p, float smallRadius, float largeRadius)

A circle line. Can also be used to make a torus by subtracting the smaller radius of the torus.

float fCircle(vec3 p, float r)

A circular disc with no thickness (i.e. a cylinder with no height). Subtract some value to make a flat disc with rounded edge.

float fDisc(vec3 p, float r)

Hexagonal prism, circumcircle variant

float fHexagonCircumcircle(vec3 p, vec2 h)

Hexagonal prism, incircle variant

float fHexagonIncircle(vec3 p, vec2 h)

Cone with correct distances to tip and base circle. Y is up, 0 is in the middle of the base.

float fCone(vec3 p, float radius, float height)

"Generalized Distance Functions" by Akleman and Chen.

see the Paper at https://www.viz.tamu.edu/faculty/ergun/research/implicitmodeling/papers/sm99.pdf

This set of constants is used to construct a large variety of geometric primitives. Indices are shifted by 1 compared to the paper because we start counting at Zero. Some of those are slow whenever a driver decides to not unroll the loop, which seems to happen for fIcosahedron und fTruncatedIcosahedron on nvidia 350.12 at least. Specialized implementations can well be faster in all cases.

const vec3 GDFVectors[19]

Version with variable exponent. This is slow and does not produce correct distances, but allows for bulging of objects.

float fGDF(vec3 p, float r, float e, int begin, int end)

Version with without exponent, creates objects with sharp edges and flat faces

float fGDF(vec3 p, float r, int begin, int end)

Primitives constructed with GDF follow:

float fOctahedron           (vec3 p, float r, float e)
float fDodecahedron         (vec3 p, float r, float e)
float fIcosahedron          (vec3 p, float r, float e)
float fTruncatedOctahedron  (vec3 p, float r, float e)
float fTruncatedIcosahedron (vec3 p, float r, float e)
float fOctahedron           (vec3 p, float r)
float fDodecahedron         (vec3 p, float r)
float fIcosahedron          (vec3 p, float r)
float fTruncatedOctahedron  (vec3 p, float r)
float fTruncatedIcosahedron (vec3 p, float r)

Domain Manipultion Operators

Conventions:

Everything that modifies the domain is named pSomething.

Many operate only on a subset of the three dimensions. For those, you must choose the dimensions that you want manipulated by supplying e.g. p.x or p.zx

inout p is always the first argument and modified in place.

Many of the operators partition space into cells. An identifier or cell index is returned, if possible. This return value is intended to be optionally used e.g. as a random seed to change parameters of the distance functions inside the cells.

Unless stated otherwise, for cell index 0, p is unchanged and cells are centered on the origin so objects don't have to be moved to fit.

Rotate around a coordinate axis (i.e. in a plane perpendicular to that axis) by angle a. Read like this: pR(p.xz, a) rotates "x towards z". This is fast if a is a compile-time constant and slower (but still practical) if not.

void pR(inout vec2 p, float a)

Shortcut for 45-degrees rotation

void pR45(inout vec2 p)

Repeat space along one axis. Use like this to repeat along the x axis: float cell = pMod1(p.x,5); - using the return value is optional.

float pMod1(inout float p, float size)

Same, but mirror every second cell so they match at the boundaries

float pModMirror1(inout float p, float size)

Repeat the domain only in positive direction. Everything in the negative half-space is unchanged.

float pModSingle1(inout float p, float size)

Repeat only a few times: from indices start to stop (similar to above, but more flexible)

float pModInterval1(inout float p, float size, float start, float stop)

Repeat around the origin by a fixed angle. For easier use, num of repetitions is use to specify the angle.

float pModPolar(inout vec2 p, float repetitions)

Repeat in two dimensions

vec2 pMod2(inout vec2 p, vec2 size)

Same, but mirror every second cell so all boundaries match

vec2 pModMirror2(inout vec2 p, vec2 size)

Same, but mirror every second cell at the diagonal as well

vec2 pModGrid2(inout vec2 p, vec2 size)

Repeat in three dimensions

vec3 pMod3(inout vec3 p, vec3 size)

Mirror at an axis-aligned plane which is at a specified distance dist from the origin.

float pMirror (inout float p, float dist)

Mirror in both dimensions and at the diagonal, yielding one eighth of the space. translate by dist before mirroring.

vec2 pMirrorOctant (inout vec2 p, vec2 dist)

Reflect space at a plane

float pReflect(inout vec3 p, vec3 planeNormal, float offset)

Object Combination Operators

We usually need the following boolean operators to combine two objects:

  • Union: OR(a,b)
  • Intersection: AND(a,b)
  • Difference: AND(a,!b)

(a and b being the distances to the objects).

The trivial implementations are min(a,b) for union, max(a,b) for intersection and max(a,-b) for difference. To combine objects in more interesting ways to produce rounded edges, chamfers, stairs, etc. instead of plain sharp edges we can use combination operators. It is common to use some kind of "smooth minimum" instead of min(), but we don't like that because it does not preserve Lipschitz continuity in many cases.

Naming convention: since they return a distance, they are called fOpSomething. The different flavours usually implement all the boolean operators above and are called fOpUnionRound, fOpIntersectionRound, etc.

The basic idea: Assume the object surfaces intersect at a right angle. The two distances a and b constitute a new local two-dimensional coordinate system with the actual intersection as the origin. In this coordinate system, we can evaluate any 2D distance function we want in order to shape the edge.

The operators below are just those that we found useful or interesting and should be seen as examples. There are infinitely more possible operators.

They are designed to actually produce correct distances or distance bounds, unlike popular "smooth minimum" operators, on the condition that the gradients of the two SDFs are at right angles. When they are off by more than 30 degrees or so, the Lipschitz condition will no longer hold (i.e. you might get artifacts). The worst case is parallel surfaces that are close to each other.

Most have a float argument r to specify the radius of the feature they represent. This should be much smaller than the object size.

Some of them have checks like if ((-a < r) && (-b < r)) that restrict their influence (and computation cost) to a certain area. You might want to lift that restriction or enforce it. We have left it as comments in some cases.

usage example:

float fTwoBoxes(vec3 p) {
  float box0 = fBox(p, vec3(1));
  float box1 = fBox(p-vec3(1), vec3(1));
  return fOpUnionChamfer(box0, box1, 0.2);
}

The "Chamfer" flavour makes a 45-degree chamfered edge (the diagonal of a square of size r):

float fOpUnionChamfer(float a, float b, float r)

Intersection has to deal with what is normally the inside of the resulting object when using union, which we normally don't care about too much. Thus, intersection implementations sometimes differ from union implementations.

float fOpIntersectionChamfer(float a, float b, float r)

Difference can be built from Intersection or Union:

float fOpDifferenceChamfer (float a, float b, float r)

The "Round" variant uses a quarter-circle to join the two objects smoothly:

float fOpUnionRound(float a, float b, float r)
float fOpIntersectionRound(float a, float b, float r)
float fOpDifferenceRound (float a, float b, float r)

The "Columns" flavour makes n-1 circular columns at a 45 degree angle:

float fOpUnionColumns(float a, float b, float r, float n) 
float fOpDifferenceColumns(float a, float b, float r, float n)
float fOpIntersectionColumns(float a, float b, float r, float n)

The "Stairs" flavour produces n-1 steps of a staircase: much less stupid version by paniq

float fOpUnionStairs(float a, float b, float r, float n)

We can just call Union since stairs are symmetric.

float fOpIntersectionStairs(float a, float b, float r, float n)
float fOpDifferenceStairs(float a, float b, float r, float n)

Similar to fOpUnionRound, but more lipschitz-y at acute angles (and less so at 90 degrees). Useful when fudging around too much by MediaMolecule, from Alex Evans' siggraph slides

float fOpUnionSoft(float a, float b, float r)

produces a cylindical pipe that runs along the intersection. No objects remain, only the pipe. This is not a boolean operator.

float fOpPipe(float a, float b, float r)

first object gets a v-shaped engraving where it intersect the second

float fOpEngrave(float a, float b, float r)

first object gets a capenter-style groove cut out

float fOpGroove(float a, float b, float ra, float rb)

first object gets a capenter-style tongue attached

float fOpTongue(float a, float b, float ra, float rb)