Sunday, July 3, 2022
HomeGame DevelopmentCocos Creator Implements Numerous Shader Results Primarily based On SDF - Information...

Cocos Creator Implements Numerous Shader Results Primarily based On SDF – Information base


Introduction

Developer muzzik shares his understanding of SDF with us and makes use of SDF to skillfully obtain results akin to shadows, strokes, and outer glow.

The complete identify of SDF is Signed Distance Area, which represents the space from every level in house to the floor of the thing.

• Signed: Refers to optimistic and unfavorable numbers. Constructive numbers signify exterior the thing, and unfavorable numbers signify inside the thing.

• Distance subject: The worth in it represents the space to the thing’s floor, and 0 represents the thing’s floor. For instance, a price of 5 implies that the present level is exterior the thing, and there may be nonetheless a distance of 5 from the floor, and a unfavorable quantity is the other.

SDF is commonly utilized in font rendering, Ray Marching, physics engines, and different fields. Right this moment we are going to implement some Shader results primarily based on SDF, together with deformation animation, stroke, outer glow, shadow, and many others.

Pre-knowledge

To facilitate these with no shader basis of information, here’s a temporary introduction to the essential understanding of shader; on the similar time, the GLSL built-in capabilities used on this article are defined.

A shader is definitely a GLSL (OpenGL Shading Language) program, and WebGL is an OpenGL packaged for the comfort of browsers.

Composition:

• Vertex shader: The mannequin consists of triangular faces, and the triangular faces are composed of vertices, and the vertex shader is accountable for the coordinate management of the vertices, which can be utilized to implement material, water, and so forth.

• Fragment Shader: The fragment shader is accountable for the colour output of the render place.

Description of the GLSL built-in capabilities used on this article:

• clamp(x, y, z): x < y returns y, x > z returns z, in any other case returns x

• combine(x, y, z): Linear aliasing of x, y, x(1 – z) + y * z

• size(x): Returns the modulo (size) of a vector, ie. sqrt(dot(x,x))

• signal(x): -1 if x < 0, 0 if x == 0, 1 if x > 0

Deformation animation

Draw a circle

If we need to draw a circle with SDF contained in the Shader, how ought to we do it? It’s easy. The code is as follows.

  • The parameter p is the place of the present rendering level as a result of it’s 2D graphics, so solely x,y.

  • The parameter r is the radius of the circle we need to draw.

The returned consequence right here is the space subject.

For instance, the radius of the circle is 5. The circle is at 0,0 (all formulation are primarily based on 0,0), the rendering level is at 0,3, then size ( p ) = 3, 3 – 5 = -2, then We’ve a distance of two from the floor of the circle, a unfavorable quantity means the rendered level is inside the thing.

Then all we have now to do is “draw” it within the fragment shader:

  • output_v4: The colour of the fragment shader output

  • float dist_f: distance subject

  • vec4 color_v4: object coloration

output_v4 = combine(output_v4, color_v4, clamp(-dist_f, 0.0, 1.0));
  • dist_f: Adverse is optimistic, so inside the thing clamp ends in a sound worth, exterior the thing is a unfavorable quantity (clamp ends in 0), the ultimate result’s the unique output_v4, so solely objects inside the thing combine shall be efficient.

Word: Under, I consult with the SDF worth as the space subject. Extra SDF graphics formulation and ideas are connected on the finish of the article, and buddies can proceed to study extra.

Pan

Pan

I discussed how to attract SDF graphics, so how one can make them transfer? It’s simple. We solely have to subtract the coordinates we need to transfer from the rendering level, cross the consequence level into the SDF operate to acquire the space subject, after which get the moved distance subject.

vec2 translate(vec2 render_v2_, vec2 move_v2_) {
return render_v2_ - move_v2_;
}

for instance:

float dist_f = sdf_circle(translate(render_v2_, vec2(100.0, 100.0)), 10.0);

dist_f is the space subject after acquiring the interpretation vec2(100.0, 100.0) via the SDF operate.

Rotate

rotate

Rotation is definitely fairly easy. Builders who’ve studied matrices ought to know that there’s a rotation matrix. We solely have to convert the vector * two-dimensional rotation matrix to get the rotated level:

// Counterclockwise rotation
vec2 rotate_ccw(vec2 render_v2_, float radian_f_) {
mat2 m = mat2(cos(radian_f_), sin(radian_f_), -sin(radian_f_), cos(radian_f_));
return render_v2_ * m;
}

// Clockwise rotation
vec2 rotate_cw(vec2 render_v2_, float radian_f_) {
mat2 m = mat2(cos(radian_f_), -sin(radian_f_), sin(radian_f_), cos(radian_f_));
return render_v2_ * m;
}

Show a number of objects

multiple
Word: Residual pixels are associated to display screen recording software program

If you wish to show a number of SDF objects usually, you solely have to return the one with the smallest distance subject of the 2. One min is drafted:

float merge(float dist_f_, float dist2_f_) {
return min(dist_f_, dist2_f_);
}

These results are easy! By manipulating the space subject, we are able to get extra results. Please see under.

Intersect

intersec t

Isn’t the impact bizarre? This operate will solely return < 0 when the space fields of the 2 objects are < 0 on the similar time. The strategy can be simple:

float intersect(float dist_f_, float dist2_f_) {
 // dist_f_ < 0, dist2_f_ > 0  Instance dist_f_ = -2, dist2_f_ = 3,r = 3, Instance dist_f_ = -2, dist2_f_ = 1,r = 1, then the worth is > 0
 // dist_f_ > 0, dist2_f_ < 0 Instance dist_f_ = 2, dist2_f_ = -1, r = 2, Instance dist_f_ = 2, dist2_f_ = -5, r = 2, then the worth is > 0
// dist_f_ > 0, dist2_f_ > 0 Instance dist_f_ = 1, dist2_f_ = 2, r = 2, Instance dist_f_ = 2, dist2_f_ = 1, r = 2, then the worth is > 0
// dist_f_ < 0, dist2_f_ < 0 Instance dist_f_ = -2, dist2_f_ = -3, r = -2, Instance dist_f_ = -2, dist2_f_ = -1, r = -1, then the worth is < 0
// So the ultimate consequence will solely be proven when dist_f_ and dist2_f_ coincide
 return max(dist_f_, dist2_f_);
}

The precept is that max will return a unfavorable quantity solely when two numbers are < 0 on the similar time, so the above impact is precipitated.

Fusion

fusion

This impact is comparatively widespread, and the implementation is as follows:

float smooth_merge(float dist_f_, float dist2_f_, float k_f_) {
 // k_f_ is invalid (0 or 1) if it doesn't exceed abs(dist_f_ - dist2_f_)
    float h_f = clamp(0.5 + 0.5 * (dist2_f_ - dist_f_) / k_f_, 0.0, 1.0);
 // Suppose k_f_ = 0, dist_f_ = 2, dist2_f_ = 1, then h_f = 0, combine(...) = dist2_f_, k_f_ * h_f * (1.0 - h_f) = 0 then the result's dist2_f_
 // Suppose k_f_ = 0, dist_f_ = 1, dist2_f_ = 2, then h_f = 1, combine(...) = dist_f_, k_f_ * h_f * (1.0 - h_f) = 0 then the result's dist_f_
 // If k_f_ is invalid, then the consequence shall be = min(dist_f_, dist2_f_), the identical because the merge consequence
 // if k_f_ is legitimate, then the consequence shall be smaller than min(dist_f_, dist2_f_), the bigger the k_f_, the smaller the consequence
    return combine(dist2_f_, dist_f_, h_f) - k_f_ * h_f * (1.0 - h_f);
}

As might be seen from the above, the consequence will solely be operated when k_f_ > abs(dist_f_ – dist2_f_). If the incoming dist_f_ and dist2_f_ outcomes will not be a lot completely different, then it will likely be lower than k_f_, in order that the center place of the 2 objects’ returned worth is bigger.

Offset

offset

The overlapping a part of the 2 throughout the motion disappears, and that is the offset impact:

float merge_exclude(float dist_f_, float dist2_f_) {
 // if dist_f_ < 0, dist2_f_ > 0 Instance dist_f_ = -2 dist2_f_ = 6, r = -2, Instance dist_f_ = -2 dist2_f_ = 3, r = -2
 // if dist_f_ > 0, dist2_f_ < 0 Instance dist_f_ = 2 dist2_f_ = -6, r = -6, Instance dist_f_ = -2 dist2_f_ = 3, r = -2
 // if dist_f_ > 0, dist2_f_ > 0 Instance dist_f_ = 2 dist2_f_ = 6, r = 2, Instance dist_f_ = 5 dist2_f_ = 3, r = 3
 // if dist_f_ < 0, dist2_f_ < 0 Instance dist_f_ = -2 dist2_f_ = -3, r = 4, Instance dist_f_ = -3 dist2_f_ = -2, r = 4
 // so the ultimate consequence will solely flip dist_f_ < 0 && dist2_f_ < 0 into > 0
 return min(max(-dist_f_, dist2_f_), max(-dist2_f_, dist_f_));

The last word objective is to vary the worth of dist_f_ < 0 && dist2_f_ < 0 to a price > 0 so that it’s going to get a optimistic quantity exterior the thing to realize the offset impact.

Minus

minus

The impact of “subtracting” is identical because the literal which means, subtracting the overlapping a part of one other object, in fact, the subtracted object is not going to be displayed, in any other case it’s going to turn out to be an offset impact:

float substract(float dist_f_, float dist2_f_) {
 // dist_f_ < 0, dist2_f_ > 0 Instance dist_f_ = -2, dist2_f_ = 3, r = 3, Instance dist_f_ = -2, dist2_f_ = 1, r = 2, then worth > 0
 // dist_f_ > 0, dist2_f_ < 0 Instance dist_f_ = 2, dist2_f_ = -1, r = -1, Instance dist_f_ = 2, dist2_f_ = -5, r = -2, then worth < 0
 // dist_f_ > 0, dist2_f_ > 0 Instance dist_f_ = 1, dist2_f_ = 2, r = 2, Instance dist_f_ = 2, dist2_f_ = 1, r = 1, then worth > 0
 // dist_f_ < 0, dist2_f_ < 0 Instance dist_f_ = -2, dist2_f_ = -3, r = 4, Instance dist_f_ = -2, dist2_f_ = -1, r = 4, then worth > 0
 // so the ultimate consequence will solely present dist2_f_, and never when dist_f_ and dist2_f_ coincide
return max(-dist_f_, dist2_f_);

From the above instance, it may be seen that solely dist_f_ > 0 && dist2_f_ < 0 returns the worth < 0, whereas different circumstances consequence > 0.

  • dist_f_ > 0, dist2_f_ < 0 return < 0 means the rendering level isn’t within the first object and is displayed within the second object

  • And dist_f_ > 0, dist2_f_ < 0 returns > 0, which implies that the rendering level is in two objects on the similar time, that’s, the offset impact

Stroke

image

Along with attaining completely different show results via distance fields, we are able to additionally use distance fields for mixing to realize object strokes. Only one line of code to do it:

  • output_v4: The colour of the fragment shader output
  • float dist_f: distance subject
  • vec4 color_v4: stroke coloration
  • float width_f: stroke width
output_v4 = combine(output_v4, color_v4, abs(clamp(dist_f - width_f, 0.0, 1.0) - clamp(dist_f, 0.0, 1.0)));

It may be seen from the above code that the legitimate worth of dist_f is (0~ 1.0 + width_ f), so it’s going to return a unfavorable quantity via clamp – clamp on this vary, abs will convert it to a optimistic quantity, after which combine it via combine, then Will get the blended coloration of the sides of the thing.

Inside/Exterior Glow

Outer glow

image

Radiate with energy or radiation poisoning with this impact:

  • float dist_f: distance subject
  • vec4 color_v4_: the colour of the rendered level
  • vec4 input_color_v4_: Outer glow coloration
  • float radius_f_: outer glow radius
vec4 outer_glow(float dist_f_, vec4 color_v4_, vec4 input_color_v4_, float radius_f_) {
    // dist_f_ > radius_f_ ends in 0
    // dist_f_ < 0 ends in 1
    // dist_f_ > 0 && dist_f_ < radius_f_ then the bigger the dist_f_ the smaller the a_f, vary 0 ~ 1
    float a_f = abs( clamp(dist_f_ / radius_f_, 0.0, 1.0) - 1.0);
    // pow: smoothing a_f
    // max and min: stop rendering inside the thing
    float b_f = min(max(0.0, dist_f_), pow(a_f, 5.0)).
    Returns color_v4_ + input_color_v4_ * b_f;
}

The vary of legitimate values for dist_f_ is ( 0 ~ radius ).

  • If dist_f_ > radius_f_:
    a_f = 0;
    b_f = min(max(0.0, dist_f_), 0) = 0;
    The return worth is color_v4_, which is invalid presently.

  • If dist_f_ < 0:
    a_f = 1;
    b_f = min(max(0.0, dist_f_), 1) = 0;
    The return worth is color_v4_, which is invalid presently.

Internal glow

image

The creation of the interior glow impact is rewritten based on the above outer glow:

vec4 inner_glow(float dist_f_, vec4 color_v4_, vec4 input_color_v4_, float radius_f_) {
    // (dist_f_ + radius_f_) > radius_f_ ends in 1
    // (dist_f_ + radius_f_) < 0 Result's 0
    // (dist_f_ + radius_f_) > 0 && (dist_f_ + radius_f_) < radius_f_ then the bigger the dist_f_ the bigger the a_f, vary 0 ~ 1
    float a_f = clamp((dist_f_ + radius_f_) / radius_f_, 0.0, 1.0);
    // pow: smoothing a_f
    // 1.0+: render inside the thing
    // max(1.0, signal(dist_f_) * -: returns -1 if dist_f_ < 0, 0 if dist_f_ == 0, 1 if dist_f_ > 0, so legitimate values are inside the thing solely
    float b_f = 1.0 - max(1.0, signal(dist_f_) * -(1.0 + pow(a_f, 5.0)));
  return color_v4_ + input_color_v4_ * b_f;
}
  • If (dist_f_ + radius_f_) > radius_f_ :

a_f = 1.0;

b_f = 1.0 – max(1.0, -2.0) = 0;

The return worth is color_v4_, which is invalid presently.

  • If (dist_f_ + radius_f_) < 0:

a_f = 0.0;

b_f = 1.0 – max(1.0, 1.0) = 0;

The return worth is color_v4_, which is invalid presently.

Since dist_f will get smaller because it goes inside the thing, it additionally causes a_f to do the identical, so the final 1.0 – max.

Shadow

Laborious shadow

hardshadow
Word: Residual pixels are associated to display screen recording software program

What are exhausting shadows? Shadows with out transitions across the edges are exhausting shadows. Our SDF can’t solely generate all types of graphics on the similar time but in addition do shadows!

The implementation precept of exhausting shadows is: from the rendering level to the sunshine supply level, step in flip the protected distance (SDF distance subject, representing this vary is not going to contact the thing), if the space subject < 0, it implies that the thing is touched, return 0, after which the colour *= return worth of our mild supply, we get the shadow.

Go on to the code:

  • vec2 render_v2_ render level
  • vec2 light_v2_ mild level
float shadow(vec2 render_v2_, vec2 light_v2_) {
  // the route vector from the present render place to the sunshine supply place
    vec2 render_to_light_dir_v2 = normalize(light_v2_ - render_v2_);
  // distance from rendering place to mild supply place
    float render_to_light_dist_f = size(render_v2_ - light_v2_);
  // journey distance
    float travel_dist_f = 0.01;
    for (int k_i = 0; k_i < max_shadow_step; ++k_i) {    
      // distance from rendering level to scene
      float dist_f = scene_dist(render_v2_ + render_to_light_dir_v2 * travel_dist_f);
      // Lower than 0 means inside the thing
      if (dist_f < 0.0) {
        return 0.0;
      }

      // abs: keep away from going backwards
      // max keep away from rendering factors too near the bodily floor leading to a really small exhaustion of traversals, so it's attainable to skip drawing shadows the place the thing distance is lower than 1.0

      travel_dist_f += max(1.0, abs(dist_f));

      // travel_dist_f += abs(dist_f); precise shadows
      // Render level distance over mild supply level

      if (travel_dist_f > render_to_light_dist_f) {
        return 1.0;
      }
    }
    return 0.0;
  }

Delicate shadow

image
determine 1

soft shadow
determine 2

Delicate shadows are extra sensible than exhausting shadows. There are two varieties of sentimental shadows in SDF that I perceive at present. One is the formulation talked about in iq and games202, however the impact isn’t good. When it’s near the thing, it’s going to produce mushy curved shadows (image 1 above); This text will consult with the code of one other nice developer on Shadertoy, and the impact is excellent (image 2 above).

Code first:

float shadow(vec2 render_v2_, vec2 light_v2_, float hard_f_) {
  // the route vector from the present render place to the sunshine supply place
  vec2 render_to_light_dir_v2 = normalize(light_v2_ - render_v2_);
  // distance from rendering place to mild supply place
  float render_to_light_dist_f = size(render_v2_ - light_v2_);
  // a part of the seen mild, ranging from a radius (the decrease half is added final).
  float brightness_f = hard_f_ * render_to_light_dist_f;
  // distance traveled
  float travel_dist_f = 0.01;
  for (int k_i = 0; k_i < max_shadow_step; ++k_i) {    
  // distance from the present place to the scene
  float dist_f = scene_dist(render_v2_ + render_to_light_dir_v2 * travel_dist_f);
  // render level inside the thing
  if (dist_f < -hard_f_) {
   return 0.0;
  }

  // dist_f stays the identical, the smaller the brightness_f, the smaller the brightness_f because it will get nearer to the sunshine supply and object
  brightness_f = min(brightness_f, dist_f / travel_dist_f);

  // max avoids rendering factors too near the bodily floor leading to a really small exhaustion of traversal instances, so it's attainable to skip shadows drawn with object distances lower than 1.0
  // abs Keep away from going backwards

  travel_dist_f += max(1.0, abs(dist_f));

  // render factors at distances larger than the sunshine supply level

  if (travel_dist_f > render_to_light_dist_f) {
   break;
  }
 }

 // brightness_f * render_to_light_dist_f is smoothed based on distance, the nearer to the sunshine supply the smaller the consequence, eliminating ripple strains
 // zoom in on the shadow, the bigger the hard_f the smaller the consequence, the bigger the shadow, hard_f_ / (2.0 * hard_f_) makes the consequence near 0.5, used to clean the transition

 brightness_f = clamp((brightness_f * render_to_light_dist_f + hard_f_) / (2.0 * hard_f_), 0.0, 1.0);
 brightness_f = smoothstep(0.0, 1.0, brightness_f);
 return brightness_f;
}

The precept of this implementation is: from the rendering level to the sunshine supply level, step-by-step, the protected distance (SDF distance subject, which implies that this vary is not going to contact the thing), if the space subject < -hard_f_ returns 0, why is -hard_f_, as a result of we need to draw the shadow with a hard_f_ distance from the floor of the thing in order that the mushy shadow can transition into the vary of the exhausting shadow and look extra sensible.


The Demo of this text is positioned within the Gitee repository:

We’ve glorious reference hyperlinks that can assist you if you wish to study extra.

References

Extra SDF graph formulation

Clarification of the precept of graphic formulation

https://weblog.csdn.web/qq_41368247/article/particulars/106194092

shadertoy 2D mushy shadow implementation

Delicate shadows and exhausting shadows

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments