Draw rectangular, rectangular, circular, and circular boxes using shader (WebGL-Shader Development Foundation 02)

1. Draw a rectangle

Last article learned how to draw a line, now learn how to draw a surface. Start with drawing a rectangle. In fact, the idea of drawing a rectangle is the same as that of drawing a line, except that the area restricted by drawing a line is narrower and the area restricted by drawing a rectangle is wider. Let's start with the code for drawing the first rectangle.

1.1 Draw using if statement

As you've said before in mind, this is just a practice. We encapsulate a function, box0, that draws a rectangle. The process is to define the values of the four boundaries. If the coordinates of the rectangular area are satisfied, it returns 1.0, otherwise it returns 0.0.

float box0(vec2 st){
  float left = 0.0;
  float right = 0.4;
  float top = 0.6;
  float bottom = 0.2;
  if(st.x > left && st.x < right && st.y > bottom && st.y < top){
    return 1.0;
  }else{
    return 0.0;
  } 
}

Call Procedure

pct = box0(st);
color = mix(color,line_color,pct);

The result is as follows, drawing a yellow rectangle based on the four boundaries defined

1.2 Drawing with step function

Drawing with the step function is actually the first way to use the step function conversion. By the way, avoid using the if judgment in writing the shader as much as possible, because it takes a lot of performance to make the if judgment in the piecewise processing, this time encapsulated as the function box1

float box1(vec2 st){

  float left = 0.0;
  float right = 0.4;
  float top = 0.6;
  float bottom = 0.2;

  //Left and right boundary
  float x1 = step(left,st.x);
  float x2 = step(right,1.0-st.x); //The detection value should be less than the right boundary before returning 1.0, so use 1.0-st.x
  
  //Upper and lower boundaries
  float y1 = step(bottom,st.y);
  float y2 = step(top,1.0-st.y);//Detection values less than the upper boundary should return 1.0, so use 1.0-st.y

  float pct = x1 * x2 *y1 *y2;
  return pct;
}

There are no more calls, just replace the function name, and the result is as follows

1.3 Drawing using the step function simplification method

The above functions are processed separately using screen coordinates st.x and st.y. Can they be merged or not? The answer is yes, but first the lower left boundary is processed, then the upper right boundary is processed, and then they are merged by multiplication, as follows:

float box2(vec2 st){

  float left = 0.0;
  float right = 0.4;
  float top = 0.6;
  float bottom = 0.2;

  //Lower Left Boundary
  vec2 bl = step(vec2(left,bottom),st);
  float pct = bl.x * bl.y;


  //Upper Right Boundary
  vec2 tr = step(vec2(right,top),1.0-st);//The detection value should be less than the upper right boundary before returning 1.0, so use 1.0-st
  pct *= tr.x * tr.y;

  return pct;
}

results of enforcement

1.4 Drawing with abs symmetry

In fact, the method of drawing rectangle can be further simplified. Using the idea of symmetry through the origin with abs function, the rectangle can be drawn simply by restricting the upper and right boundaries, as follows:

float box3(vec2 st){

   float right = 0.9;
   float top = 0.3;

   //Draw a symmetrical quadrilateral with an origin in the upper right corner
   vec2 bl = 1.0-step(vec2(right,top),abs(st));
   float pct = bl.x * bl.y;

   return pct;
 }

give the result as follows

1.5 Draw rectangular border

The fourth method is the smallest and taller code, on which we can deduct a smaller rectangle and get a rectangular border as follows

float box4(vec2 st){

  float right = 0.9;
  float top = 0.3;
  float line_width = 0.03;
  
  //Draw a symmetrical quadrilateral with an origin in the upper right corner
  vec2 b1 = 1.0-step(vec2(right,top),abs(st));

  float boxouter = b1.x * b1.y;

  vec2 b2 = 1.0-step(vec2(right-line_width,top-line_width),abs(st));
  float boxinner = b2.x * b2.y;

  float pct = boxouter -boxinner;

  return pct;
}

The results are as follows

If you are not satisfied with the result of drawing, you can adjust the width of the border and wireframe. Some friends may ask you what to do if I don't want it to go through the origin, but want to draw in this way. You can draw it first and then pan it. Panning is not mentioned here. Write an article later that will focus on panning, rotating, zooming of the graphic.

2. Draw a circle

2.1 Draw a circle

The idea of drawing a circle is the same. The circle is defined as a set of points whose distance from a point is equal to a fixed length. We use distance(), length() or sqrt() to draw a circle, this time using the smoothstep function, in order to eliminate aliasing, as follows

float circle(vec2 st,vec2 center,float radius) { 
  float blur = 0.002;

  //float pct = distance(st,center);// Calculate the distance from any point to the center of a circle

  vec2 tC = st-center; //Compute the vector from the center of a circle to any point
  //float pct = length(tC); // Finding Length Using the Length Function
  float pct = sqrt(tC.x*tC.x+tC.y*tC.y);//Find Length by Squaring

  return 1.0-smoothstep(radius,radius+blur,pct);
}  

give the result as follows

2.2 Draw a circular box

Draw a circle, drawing a circle box is to drop a smaller circle on the circle, as follows

float circleLine(vec2 st,vec2 center,float radius) { 
  float pct = distance(st,center);//Calculate the distance from any point to the center of a circle
  float line_width = 0.02;
  float radius2 = radius-line_width;
  float blur = 0.002;
  return (1.0-smoothstep(radius-blur,radius+blur,pct))-(1.0-smoothstep(radius2-blur,radius2+blur,pct));
}

give the result as follows

3. demo code

Old rule, demo all code attached

<body>
  <div id="container"></div>
  <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/build/three.js"></script>
  <script>
    var container;
    var camera, scene, renderer;
    var uniforms;
    var vertexShader = `
      void main() {
        gl_Position = vec4( position, 1.0 );
      } 
    `
    var fragmentShader = `
    #ifdef GL_ES
    precision mediump float;
    #endif
    uniform float u_time;
    uniform vec2 u_mouse;
    uniform vec2 u_resolution;


    float box0(vec2 st){
      float left = 0.0;
      float right = 0.4;
      float top = 0.6;
      float bottom = 0.2;
      if(st.x > left && st.x < right && st.y > bottom && st.y < top){
        return 1.0;
      }else{
        return 0.0;
      } 
    }

    float box1(vec2 st){

      float left = 0.0;
      float right = 0.4;
      float top = 0.6;
      float bottom = 0.2;

      //Left and right boundary
      float x1 = step(left,st.x);
      float x2 = step(right,1.0-st.x); //The detection value should be less than the right boundary before returning 1.0, so use 1.0-st.x
      
      //Upper and lower boundaries
      float y1 = step(bottom,st.y);
      float y2 = step(top,1.0-st.y);//Detection values less than the upper boundary should return 1.0, so use 1.0-st.y

      float pct = x1 * x2 *y1 *y2;
      return pct;
    }

    float box2(vec2 st){

      float left = 0.0;
      float right = 0.4;
      float top = 0.6;
      float bottom = 0.2;

      //Lower Left Boundary
      vec2 bl = step(vec2(left,bottom),st);
      float pct = bl.x * bl.y;
  

      //Upper Right Boundary
      vec2 tr = step(vec2(right,top),1.0-st);//The detection value should be less than the upper right boundary before returning 1.0, so use 1.0-st
      pct *= tr.x * tr.y;

      return pct;
    }

    float box3(vec2 st){

      float right = 0.9;
      float top = 0.3;

      //Draw a symmetrical quadrilateral with an origin in the upper right corner
      vec2 bl = 1.0-step(vec2(right,top),abs(st));
      float pct = bl.x * bl.y;

      return pct;
    }

    float box4(vec2 st){

      float right = 0.9;
      float top = 0.3;
      float line_width = 0.03;

      //Draw a symmetrical quadrilateral with an origin in the upper right corner
      vec2 b1 = 1.0-step(vec2(right,top),abs(st));

      float boxouter = b1.x * b1.y;

      vec2 b2 = 1.0-step(vec2(right-line_width,top-line_width),abs(st));
      float boxinner = b2.x * b2.y;

      float pct = boxouter -boxinner;

      return pct;
    }

    float circle(vec2 st,vec2 center,float radius) { 
      float blur = 0.002;

      //float pct = distance(st,center);// Calculate the distance from any point to the center of a circle

      vec2 tC = st-center; //Compute the vector from the center of a circle to any point
      //float pct = length(tC); // Finding Length Using the Length Function
      float pct = sqrt(tC.x*tC.x+tC.y*tC.y);//Find Length by Squaring

      return 1.0-smoothstep(radius,radius+blur,pct);
    }    

    float circleLine(vec2 st,vec2 center,float radius) { 
      float pct = distance(st,center);//Calculate the distance from any point to the center of a circle
      float line_width = 0.02;
      float radius2 = radius-line_width;
      float blur = 0.002;
      return (1.0-smoothstep(radius-blur,radius+blur,pct))-(1.0-smoothstep(radius2-blur,radius2+blur,pct));
    }

    void main( void ) {

      //Window coordinates adjusted to [-1,1], coordinate origin at the center of the screen
      vec2 st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;

      //Window coordinates adjusted to [0,1], coordinate origin in lower left corner of screen
      //vec2 st = gl_FragCoord.xy/u_resolution;

      vec3 line_color = vec3(1.0,1.0,0.0);
      vec3 color = vec3(0.6);//background color
      float pct = 0.0;

      pct = box0(st);
      pct = box1(st);
      pct = box2(st);
      pct = box3(st);
      pct = box4(st);
      //color = mix(color,line_color,pct);

      pct = circle(st,vec2(-0.3),0.4);
      //color = mix(color,line_color,pct);

      pct = circleLine(st,vec2(0.3),0.4);
      color = mix(color,line_color,pct);

      gl_FragColor = vec4(color, 1);
    }
    `

    init();
    animate();

    function init() {
      container = document.getElementById('container');

      camera = new THREE.Camera();
      camera.position.z = 1;

      scene = new THREE.Scene();

      var geometry = new THREE.PlaneBufferGeometry(2, 2);

      uniforms = {
        u_time: {
          type: "f",
          value: 1.0
        },
        u_resolution: {
          type: "v2",
          value: new THREE.Vector2()
        },
        u_mouse: {
          type: "v2",
          value: new THREE.Vector2()
        }
      };

      var material = new THREE.ShaderMaterial({
        uniforms: uniforms,
        vertexShader: vertexShader,
        fragmentShader: fragmentShader
      });

      var mesh = new THREE.Mesh(geometry, material);
      scene.add(mesh);

      renderer = new THREE.WebGLRenderer();
      //renderer.setPixelRatio(window.devicePixelRatio);

      container.appendChild(renderer.domElement);

      onWindowResize();
      window.addEventListener('resize', onWindowResize, false);

      document.onmousemove = function (e) {
        uniforms.u_mouse.value.x = e.pageX
        uniforms.u_mouse.value.y = e.pageY
      }
    }

    function onWindowResize(event) {
      renderer.setSize(800, 800);
      uniforms.u_resolution.value.x = renderer.domElement.width;
      uniforms.u_resolution.value.y = renderer.domElement.height;
    }

    function animate() {
      requestAnimationFrame(animate);
      render();
    }

    function render() {
      uniforms.u_time.value += 0.02;
      renderer.render(scene, camera);
    }
  </script>
</body>

Tags: Shader GLSL

Posted on Tue, 09 Nov 2021 12:20:22 -0500 by no3dfx