webgl shader学习 有向距离场1

感谢 https://blog.csdn.net/qq_41368247/article/details/106194092 的详尽分析,
这里使用的three.js 版本为106,

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>demo5_1 距离场 圆</title>
  <script src="../external/three.js"></script>
  <script src="../controls/OrbitControls.js"></script>
  <style>
    body {
      overflow: hidden;
      padding: 0;
      margin: 0;
    }
  </style>
</head>
<body>
<div id="container"></div>

<script id="vertexShader" type="x-shader/x-vertex">
  varying vec2 vUv;
  void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  }
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
  uniform vec2 u_resolution;
  uniform float u_time;
  varying vec2 vUv;
  uniform vec2 u_mouse;
  // Signed distance and circle

  // List of some other 2D distances:
  //
  // Circle:               https://www.shadertoy.com/view/3ltSW2
  // Segment:              https://www.shadertoy.com/view/3tdSDj
  // Triangle:             https://www.shadertoy.com/view/XsXSz4
  // Isosceles Triangle:   https://www.shadertoy.com/view/MldcD7
  // Regular Triangle:     https://www.shadertoy.com/view/Xl2yDW
  // Regular Pentagon:     https://www.shadertoy.com/view/llVyWW
  // Regular Octogon:      https://www.shadertoy.com/view/llGfDG
  // Rounded Rectangle:    https://www.shadertoy.com/view/4llXD7
  // Rhombus:              https://www.shadertoy.com/view/XdXcRB
  // Trapezoid:            https://www.shadertoy.com/view/MlycD3
  // Polygon:              https://www.shadertoy.com/view/wdBXRW
  // Hexagram:             https://www.shadertoy.com/view/tt23RR
  // Regular Star:         https://www.shadertoy.com/view/3tSGDy
  // Star5:                https://www.shadertoy.com/view/wlcGzB
  // Ellipse 1:            https://www.shadertoy.com/view/4sS3zz
  // Ellipse 2:            https://www.shadertoy.com/view/4lsXDN
  // Quadratic Bezier:     https://www.shadertoy.com/view/MlKcDD
  // Uneven Capsule:       https://www.shadertoy.com/view/4lcBWn
  // Vesica:               https://www.shadertoy.com/view/XtVfRW
  // Cross:                https://www.shadertoy.com/view/XtGfzw
  // Pie:                  https://www.shadertoy.com/view/3l23RK
  // Arc:                  https://www.shadertoy.com/view/wl23RK
  // Horseshoe:            https://www.shadertoy.com/view/WlSGW1
  // Parabola:             https://www.shadertoy.com/view/ws3GD7
  // Parabola Segment:     https://www.shadertoy.com/view/3lSczz
  // Rounded X:            https://www.shadertoy.com/view/3dKSDc
  // Joint:                https://www.shadertoy.com/view/WldGWM
  // Simple Egg:           https://www.shadertoy.com/view/Wdjfz3
  //
  // and many more here:   http://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
  float sdCircle( in vec2 p, in float r ) {
    return length(p)-r;
  }

  float sdBox( in vec2 p, in vec2 b )
  {
    vec2 d = abs(p)-b;
    return length(max(d,0.0)) + min(max(d.x,d.y),0.0);
//    return length(max(d,0.0)) + max(d.x,d.y);
//    return max(d.x, max(d.y, 0.0)) + min(max(d.x,d.y),0.0);
  }

  float sdSegment( in vec2 p, in vec2 a, in vec2 b )
  {
    vec2 pa = p-a, ba = b-a;
    float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
    return length( pa - ba*h );
  }

  float ndot(vec2 a, vec2 b ) { return a.x*b.x - a.y*b.y; }
  float sdRhombus( in vec2 p, in vec2 b) {
    vec2 q = abs(p);  // 坐标轴对称
    // 计算的是线段b的中点到p点的向量,在b上的投影限制到[-1, 1]
    // 负数表示向量偏向于y轴, 正数表示偏向x轴
    float h = clamp((-2.0*ndot(q,b)+ndot(b,b))/dot(b,b),-1.0, 1.0);
    /* 实际上h从-1到1的滑动过程,[0.5*b*vec2(1.0-h,1.0+h)] 表示一条从原点出发,
       终点在向量b上由左上到右下的滑动的向量 */
    float d = length( q - 0.5*b*vec2(1.0-h,1.0+h) );
    // 符号:可计算(b.x, -b.y)和(p.x, p.y-b.y)的叉积,得两向量的相对位置
    return d * sign( q.x*b.y + q.y*b.x - b.x*b.y );
  }

  float sdRhombus0( in vec2 p, in vec2 b) {
    // 参考线段的SDF
    vec2 a = vec2(0.0, b.y); // 与y轴交点
    vec2 c = vec2(b.x, 0.0);
    vec2 q = abs(p);  // 坐标轴对称
    vec2 pa = q-a, ba = c-a;
    float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
//    float h = clamp( dot(pa,ba)/dot(ba,ba), -1.0, 2.0 );
    float d = length( pa - ba*h );
    return d * sign( q.x*b.y + q.y*b.x - b.x*b.y );
  }

  /**
 * 等边三角形:  1. 原点在三角形中心点
 *              2. r表示边长的一半(不是中心点到顶点的距离)
*/
  float sdEquilateralTriangle(in vec2 p, in float r ) {
    const float k = sqrt(3.0);
    p.x = abs(p.x);
    // 由于三角形可以看成是由三个部分绕中心点三个角度生成的,x负半轴不用考虑
    // 所以只需要考虑在区域 1的那部分,这一部分可以通过关于直线y=-1/sqrt(3)*x对称映射到下方的三分之一
    // 映射关系如下:
    if( p.x+k*p.y>0.0 ) p=vec2(p.x-k*p.y,-k*p.x-p.y)/2.0;
    // 一切都归一到只需要看区域 2-3-4即可
    // x在减去r后,若其值-2*r<x<0,说明原来范围是-r<0<r,即知道点p在x轴上处于三角形内部,只需看y轴长度即可
    // 否则,需要原x轴坐标减去左/右下角坐标x值
    p.x -= r;
    p.y += r/k;
    p.x -= clamp( p.x, -2.0*r, 0.0 );
//    p.x = 0.0; p.y += r/k;
    return -length(p)*sign(p.y);
  }

  /**
* 等腰三角形:  1. 原点位于顶角,垂直于底边的高和y轴重合
*              2. q表示右腰的向量,一般q.y为负(此时顶角是朝上的)
*/
  float sdTriangleIsosceles( in vec2 p, in vec2 q )
  {
    p.x = abs(p.x);
    // 分为两种可能的最小距离: 1.与腰的距离, 2.与底的距离
    // 1. 与腰
    vec2 a = p - q*clamp( dot(p,q)/dot(q,q), 0.0, 1.0 );
    // 2. 与底
    vec2 b = p - q*vec2( clamp( p.x/q.x, 0.0, 1.0 ), 1.0 );
    float k = sign( q.y );
    float d = min(dot( a, a ), dot( b, b ));
    float s = max(k*(p.x*q.y-p.y*q.x), k*(p.y-q.y)); // 两项都小于零才是取负,即内部
    // float s = k*(p.y-q.y)>0.0?((k*(p.x*q.y-p.y*q.x))>0.0?1.0:-1.0):-1.0; // 可用此句替换
    return sqrt(d)*sign(s);
  }

  /**
* 一般三角形:  1. 原点不限
*              2. 三条边的向量都给出,为 p0,p1,p2
*/
  float sdTriangle( in vec2 p, in vec2 p0, in vec2 p1, in vec2 p2 )
  {
    vec2 e0 = p1-p0, e1 = p2-p1, e2 = p0-p2;
    vec2 v0 = p -p0, v1 = p -p1, v2 = p -p2;
    // 分别计算过p点垂直于三条边的向量,箭头指向p点
    vec2 pq0 = v0 - e0*clamp( dot(v0,e0)/dot(e0,e0), 0.0, 1.0 );
    vec2 pq1 = v1 - e1*clamp( dot(v1,e1)/dot(e1,e1), 0.0, 1.0 );
    vec2 pq2 = v2 - e2*clamp( dot(v2,e2)/dot(e2,e2), 0.0, 1.0 );
    // 由于不清楚传入的三个点是顺时针还是逆时针顺序,先确定好基本符号
    // 若s是-1,说明传入是逆时针
    // 假设规定传入都是顺时针顺序,则s为1,可无视
    float s = sign( e0.x*e2.y - e0.y*e2.x );
    // 这里的 d并非具有什么实际几何意义的向量,而是作为一个数据的集合来使用
    // 它的第一个分量d.x表示p点与各边长度的平方中的最小值,它的第二个分量可以用来判断内部外部
    // 假如点P在三角形内部,那么参与比较的三个式子都会是正数,它们中的最小值也将是正数
    // 若在外部,则至少有一个式子是负的,取最小值后d.y也将是负值
    vec2 d = min(min(vec2(dot(pq0,pq0), s*(v0.x*e0.y-v0.y*e0.x)),
    vec2(dot(pq1,pq1), s*(v1.x*e1.y-v1.y*e1.x))),
    vec2(dot(pq2,pq2), s*(v2.x*e2.y-v2.y*e2.x)));
    // 若d.y为负数,说明在三角形外,不要漏看下面一行的负号
    return -sqrt(d.x)*sign(d.y);
  }

  /**
  * y轴对称的不均匀胶囊  1. 下半圆的圆心为原点
  *                       2. h表示两个半圆的圆心之间的距离, ra为下半圆半径
  */
  float cro(in vec2 a, in vec2 b ) { return a.x*b.y - a.y*b.x; }
  float sdUnevenCapsuleY( in vec2 p, in float ra, in float rb, in float h )
  {
    p.x = abs(p.x); // 左右对称
    float cos_h = (ra-rb)/h;
    float sin_h = sqrt(1.0-cos_h*cos_h);
    // c是垂直于直线边的单位向量,直线边单位向量为(-cos_h, sin_h)
    vec2  c = vec2(sin_h, cos_h);
    // 所谓cro其实可以看成先将b逆时针旋转90°,然后再计算a和b的点乘
    // 胶囊的sdf计算同样分为三部分,上半圆+直线边+下半圆, 方法是将op向量投影到直线边上
    // 所以k就表示op投影到直线边上,可以用来判断p点处于哪块区域
    float k = cro(c,p);
    // m表示op投影到直线边逆时针旋转了90°的向量上,可用来计算p点与下圆圆心之间的距离
    float m = dot(c,p);
    // op长度的平方
    float n = dot(p,p);

    if( k < 0.0)    return sqrt(n)- ra;  // 位于下圆部分
    // 上圆,这里用到了余弦定理,h表示两圆心的长度,以及op和p点到上半圆的连线组成一个三角形,sqrt(n)*cos(op与y轴夹角)=p.y (因为h在y轴上)
    else if( k > c.x*h )    return sqrt(n+h*h-2.0*h*p.y)- rb;
    // 减去ra
    else    return m - ra;
  }
  /**
   * 普通的不均匀胶囊  1. 原点不固定
   *                  2. pa,pb表示两圆心坐标, ra,rb表示两圆半径
  */
  float sdUnevenCapsule1( in vec2 p, in vec2 pa, in vec2 pb, in float ra, in float rb )
  {
    // 思路是坐标转化,将问题转化成y轴对称的胶囊
    // 预计算
    pb -= pa; 						// pa指向pb的向量
    float h = sqrt(dot(pb,pb));     // 两圆心距离

    // 1. 坐标平移:以pa作为原点的平移,相当于将胶囊的一个圆心平移到原点
    p  -= pa;
    // 2. 坐标旋转:p.x = p.x* pb.y/h - p.y* pb.x/h
    //             p.y = p.x* pb.x/h + p.y* pb.y/h
    // 			实际上pb/h就是单位向量,其x分量就是cosθ,y分量就是sinθ
    // 			要将圆心连线旋转到与y轴平行,也就是逆时针旋转90-θ度,θ是连线与x轴夹角
    /*
    	左乘旋转矩阵:
        [cos(90-θ), -sin(90-θ)]         [sinθ,  -cosθ]       [pb.y, -pb.x]
        [sin(90-θ),  cos(90-θ)]     =   [cosθ,   sinθ]  =    [pb.x,  pb.y]
    */
    vec2  q = vec2( dot(p,vec2(pb.y,-pb.x)), dot(p,pb))/h;

    // 调用Y轴对称版本 -----------
    return sdUnevenCapsuleY(q, ra, rb, h);
  }

  void main( )
  {
    vec2 p = vUv * 2.0 - 1.0;

//    float d = sdCircle(p,0.5);
    float d = sdBox(p, vec2(0.4, 0.2));
//    float d = sdSegment(p, vec2(0.3, 0.4), vec2(-0.4, -0.3));
//    float d = sdRhombus0(p, vec2(0.6, 0.3));
//    float d = sdEquilateralTriangle(p, 0.4);
//    float d = sdTriangleIsosceles(p, vec2(0.1, -0.4));
//    float d = sdTriangle(p, vec2(-0.3, -0.4), vec2(0.3, -0.4), vec2(0.2, 0.6));
//    float d = sdUnevenCapsule1(p, vec2(-0.33, -0.21), vec2(0.25, 0.36), 0.4, 0.2);

    // coloring
    vec3 col = vec3(1.0) - sign(d)*vec3(0.1,0.4,0.7);
    col *= 1.0 - exp(-3.0*abs(d));
    col *= 0.8 + 0.2*cos(150.0*d);  // + u_time*6.0
    col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.01,abs(d)) );

    gl_FragColor = vec4(col,1.0);
  }

</script>
<script>
  //https://blog.csdn.net/qq_41368247/article/details/106194092
  var container;
  var camera, scene, renderer;
  var uniforms;
  var mesh;

  init();
  animate();

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

    camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
    camera.position.set(0, 0, 2);

    scene = new THREE.Scene();
    scene.add(new THREE.AxesHelper(20));

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

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

    var material = new THREE.ShaderMaterial( {
      uniforms: uniforms,
      side: THREE.DoubleSide,
      vertexShader: document.getElementById( 'vertexShader' ).textContent,
      fragmentShader: document.getElementById( 'fragmentShader' ).textContent
    } );
    material.extensions.derivatives = true;
    var material2 = new THREE.MeshBasicMaterial({color: '#00bbbb', wireframe: true, side: THREE.DoubleSide})

    mesh = new THREE.Mesh( geometry, material );
    scene.add( mesh );
    renderer = new THREE.WebGLRenderer();
    renderer.setPixelRatio( window.devicePixelRatio );

    container.appendChild( renderer.domElement );
    let orbit = new THREE.OrbitControls( camera, renderer.domElement );

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

  function onWindowResize( event ) {
    renderer.setSize( window.innerWidth, window.innerHeight );
    // 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.01;  //0.008
    renderer.render( scene, camera );
  }

  document.addEventListener('mousemove', onDocumentMouseMove, false);
  function onDocumentMouseMove(event) {
    let vector = new THREE.Vector3(( event.clientX / window.innerWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
    vector = vector.unproject(camera);

    let raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
    let lst = raycaster.intersectObjects([mesh]);
    if (lst.length == 0) {
      return;
    }
    let curPos = lst[0].point;
    // console.log(curPos);
    let ux = (curPos.x+1)/2, uy = (curPos.y+1)/2;
    uniforms.u_mouse.value.x = ux;
    uniforms.u_mouse.value.y = uy;
    // console.log("ux: " + ux + ", uy: " + uy);
  }
</script>
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值