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 resembling shadows, strokes, and outer glow.

The total title of SDF is Signed Distance Subject, which represents the gap from every level in area to the floor of the thing.

• Signed: Refers to optimistic and adverse numbers. Optimistic numbers signify exterior the thing, and adverse numbers signify inside the thing.

• Distance discipline: The worth in it represents the gap to the thing’s floor, and 0 represents the thing’s floor. For instance, a worth of 5 signifies that the present level is exterior the thing, and there’s nonetheless a distance of 5 from the floor, and a adverse quantity is the alternative.

SDF is commonly utilized in font rendering, Ray Marching, physics engines, and different fields. Right this moment we’ll implement some Shader results based mostly on SDF, together with deformation animation, stroke, outer glow, shadow, and so on.

Pre-knowledge

To facilitate these with no shader basis of data, here’s a temporary introduction to the fundamental understanding of shader; on the identical 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 wish 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 wish to draw.

The returned outcome right here is the gap discipline.

For instance, the radius of the circle is 5. The circle is at 0,0 (all formulation are based mostly 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 adverse quantity means the rendered level is inside the thing.

Then all we’ve to do is “draw” it within the fragment shader:

  • output_v4: The colour of the fragment shader output

  • float dist_f: distance discipline

  • vec4 color_v4: object colour

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 adverse quantity (clamp ends in 0), the ultimate result’s the unique output_v4, so solely objects inside the thing combine will likely be efficient.

Notice: Beneath, I consult with the SDF worth as the gap discipline. 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 learn how to make them transfer? It’s simple. We solely must subtract the coordinates we wish to transfer from the rendering level, move the outcome level into the SDF operate to acquire the gap discipline, after which get the moved distance discipline.

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 gap discipline 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 must 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
Notice: Residual pixels are associated to display screen recording software program

If you wish to show a number of SDF objects usually, you solely must return the one with the smallest distance discipline 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 gap discipline, 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 gap fields of the 2 objects are < 0 on the identical time. The tactic 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 outcome 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 adverse quantity solely when two numbers are < 0 on the identical time, so the above impact is prompted.

Fusion

fusion

This impact is comparatively frequent, 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 outcome will likely be = min(dist_f_, dist2_f_), the identical because the merge outcome
 // if k_f_ is legitimate, then the outcome will likely be smaller than min(dist_f_, dist2_f_), the bigger the k_f_, the smaller the outcome
    return combine(dist2_f_, dist_f_, h_f) - k_f_ * h_f * (1.0 - h_f);
}

As may be seen from the above, the outcome will solely be operated when k_f_ > abs(dist_f_ – dist2_f_). If the incoming dist_f_ and dist2_f_ outcomes should not a lot totally different, then will probably 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 through 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 outcome will solely flip dist_f_ < 0 && dist2_f_ < 0 into > 0
 return min(max(-dist_f_, dist2_f_), max(-dist2_f_, dist_f_));

The final word goal is to vary the worth of dist_f_ < 0 && dist2_f_ < 0 to a worth > 0 so that it’ll get a optimistic quantity exterior the thing to realize the offset impact.

Minus

minus

The impact of “subtracting” is identical because the literal that means, subtracting the overlapping a part of one other object, in fact, the subtracted object won’t be displayed, in any other case it is 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 outcome 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 situations outcome > 0.

  • dist_f_ > 0, dist2_f_ < 0 return < 0 means the rendering level will not be within the first object and is displayed within the second object

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

Stroke

image

Along with attaining totally 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 discipline
  • vec4 color_v4: stroke colour
  • 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 is going to return a adverse 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 colour of the perimeters of the thing.

Inside/Exterior Glow

Outer glow

image

Radiate with energy or radiation poisoning with this impact:

  • float dist_f: distance discipline
  • vec4 color_v4_: the colour of the rendered level
  • vec4 input_color_v4_: Outer glow colour
  • 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 right now.

  • 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 right now.

Interior glow

image

The creation of the inside glow impact is rewritten in response to 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 right now.

  • 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 right now.

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

Exhausting shadow

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

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

The implementation precept of laborious shadows is: from the rendering level to the sunshine supply level, step in flip the secure distance (SDF distance discipline, representing this vary won’t contact the thing), if the gap discipline < 0, it signifies that the thing is touched, return 0, after which the colour *= return worth of our gentle supply, we get the shadow.

Go on to the code:

  • vec2 render_v2_ render level
  • vec2 light_v2_ gentle level
float shadow(vec2 render_v2_, vec2 light_v2_) {
  // the course 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 gentle 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 doable 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 gentle 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 lifelike than laborious shadows. There are two varieties of sentimental shadows in SDF that I perceive presently. One is the method talked about in iq and games202, however the impact will not be good. When it’s near the thing, it is going to produce delicate 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 course 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 gentle supply place
  float render_to_light_dist_f = size(render_v2_ - light_v2_);
  // a part of the seen gentle, 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 doable 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 in response to distance, the nearer to the sunshine supply the smaller the outcome, eliminating ripple strains
 // zoom in on the shadow, the bigger the hard_f the smaller the outcome, the bigger the shadow, hard_f_ / (2.0 * hard_f_) makes the outcome near 0.5, used to easy 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 secure distance (SDF distance discipline, which signifies that this vary won’t contact the thing), if the gap discipline < -hard_f_ returns 0, why is -hard_f_, as a result of we wish to draw the shadow with a hard_f_ distance from the floor of the thing in order that the delicate shadow can transition into the vary of the laborious shadow and look extra lifelike.


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

We’ve wonderful reference hyperlinks that will help you if you wish to study extra.

References

Extra SDF graph formulation

Rationalization of the precept of graphic formulation

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

shadertoy 2D delicate shadow implementation

Delicate shadows and laborious shadows

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments