Three.js ShaderMaterial

自定义材质的使用,ShaderMaterial 和 RawShaderMaterial的区别是,前者可以使用一些通用的uniform, attribute 等等,比如
positon,uv, modelViewMatrix, modelMatrix 不需要去定义,可以直接在ShaderMaterial中使用,three.js 会自动定义好这些通用的 变量,并且时候去更新这些 内建变量,也不用去操心,由 three.js 来接管。

后者 RawShaderMaterial 没有这些内建变量,shader代码里用到的所有uniform,attribute 都需要自己去管理

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title><!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Test canvas texture</title>
      <style>
        body {
          overflow: hidden;
        }
      </style>
      <script src="./external/three.js"></script>
      <script src="./external/dat.gui.min.js"></script>
      <script src="./controls/OrbitControls.js"></script>
      <script src="j01/bitmap-sdf.js"></script>
    </head>
<body>
<script type="x-shader/x-vertex" id="vertexShader">
    uniform vec2 center;
    uniform vec3 outline_color;
    uniform float outline_width;

    varying vec2 vUv;

    varying vec4 v_outlineColor;
    varying float v_outlineWidth;

    bool isPerspectiveMatrix( mat4 m ) {

        return m[ 2 ][ 3 ] == - 1.0;

    }

    void main() {
        vUv = uv;
        vec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );

        vec2 scale;
        scale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );
        scale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );

        #ifndef USE_SIZEATTENUATION

        bool isPerspective = isPerspectiveMatrix( projectionMatrix );

        if ( isPerspective ) scale *= - mvPosition.z;

        #endif

        vec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;

        mvPosition.xy += alignedPosition;

        gl_Position = projectionMatrix * mvPosition;

        //        vec4 outlineColor = vec4(1.0, 0, 0, 0.9);
        v_outlineWidth = outline_width;
        v_outlineColor = vec4(outline_color, 1.0);
    }
</script>

<script type="x-shader/x-fragment" id="fragmentShader">
  #extension GL_OES_standard_derivatives : enable
    //1.0 - SDFSettings.CUTOFF;
    #define SDF_EDGE 0.75
    uniform float opacity;
    varying vec2 vUv;
    uniform sampler2D map;
  uniform vec3 fill_color;

    varying vec4 v_outlineColor;
    varying float v_outlineWidth;

    // Get the distance from the edge of a glyph at a given position sampling an SDF texture.
    float getDistance(vec2 position) {
        return texture2D(map, position).r;
    }

    // Samples the sdf texture at the given position and produces a color based on the fill color and the outline.
    vec4 getSDFColor(vec2 position, float outlineWidth, vec4 outlineColor, float smoothing) {
        float distance = getDistance(position);
      vec4 v_color = vec4(fill_color, 1.0);

        if (outlineWidth > 0.0) {
            // Don't get the outline edge exceed the SDF_EDGE
            float outlineEdge = clamp(SDF_EDGE - outlineWidth, 0.0, SDF_EDGE);
            float outlineFactor = smoothstep(SDF_EDGE - smoothing, SDF_EDGE + smoothing, distance);
            vec4 sdfColor = mix(outlineColor, v_color, outlineFactor);
            float alpha = smoothstep(outlineEdge - smoothing, outlineEdge + smoothing, distance);
            return vec4(sdfColor.rgb, sdfColor.a * alpha);
        } else {
            float alpha = smoothstep(SDF_EDGE - smoothing, SDF_EDGE + smoothing, distance);
            return vec4(v_color.rgb, v_color.a * alpha);
        }
    }

    void main() {

        vec3 outgoingLight = vec3( 0.0 );
        vec4 diffuseColor = vec4( vec3(1,1,1), opacity );
        vec4 texelColor = texture2D( map, vUv );

        diffuseColor *= texelColor;

        outgoingLight = diffuseColor.rgb;

        gl_FragColor = vec4( outgoingLight, diffuseColor.a );
        gl_FragColor.rgb = toneMapping( gl_FragColor.rgb );
        //gl_FragColor = linearToOutputTexel( gl_FragColor );

        // Just do a single sample
        float smoothing = 1.0/32.0;
//        float smoothing = fwidth(texelColor.r);
        vec4 color = getSDFColor(vUv, v_outlineWidth, v_outlineColor, smoothing);
        gl_FragColor = color;
    }
</script>

<script>
  function convertToSDF(canvas) {
    let ctx = canvas.getContext("2d");
    let cw = canvas.width, ch = canvas.height;
    let sdfValues = calcSDF(canvas, {
      cutoff: 0.25,
      radius: 16.0,
    });

    let imgData = ctx.getImageData(0, 0, cw, ch);
    for (let i = 0; i < cw; i++) {
      for (let j = 0; j < ch; j++) {
        let baseIndex = j * cw + i;
        let alpha = sdfValues[baseIndex] * 255;
        let imageIndex = baseIndex * 4;
        imgData.data[imageIndex + 0] = alpha;
        imgData.data[imageIndex + 1] = alpha;
        imgData.data[imageIndex + 2] = alpha;
        imgData.data[imageIndex + 3] = alpha;
      }
    }
    ctx.putImageData(imgData, 0, 0);
    return canvas;
  }
  function generateCanvas(text) {
    let canvas = document.createElement( 'canvas' );
    let context = canvas.getContext( '2d' );
    let size = 48;
    context.font = size + 'px Microsoft YaHei';
    let strLst = text.split('\n');
    let maxWdith = -Infinity;
    for (let str of strLst) {
      let measured = context.measureText(str);
      if (maxWdith < measured.width) {
        maxWdith = measured.width;
      }
    }
    canvas.width = maxWdith+20;      //根据文字内容获取宽度
    let lineHeight = size * 1.5;  // fontsize * 1.5
    canvas.height = lineHeight * strLst.length;
    // let obj = computeFontSize(text, '40px', 'Microsoft YaHei');
    // console.log("generateCanvas, ", obj);
    strLst.forEach((str, index) => {
      context.beginPath();
      context.font = size + 'px Microsoft YaHei';
      context.fillStyle = "#ccff8b";
      context.fillText(str,10,size * (1 + index));
      context.fill();
    });

    // return canvas;
    return convertToSDF(canvas);
  }

  //根据canvas图形制作sprite
  function makeCanvasSprite(canvas){
    let texture = new THREE.Texture(canvas);
    texture.needsUpdate = true;
    let uniforms = { 'opacity': {value: 1}, "center": {value: new THREE.Vector2(0.5, 0.5)},
      "map": {value:texture}, "outline_color": {value: new THREE.Color('#52ff8a')}
      , "outline_width": {value: 0.25}, "fill_color": {value: new THREE.Color('#ff3f27')}  };
    let spriteMaterial = new THREE.ShaderMaterial( {
      uniforms: uniforms,
      vertexShader: document.getElementById( 'vertexShader' ).textContent,
      fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
      side: THREE.DoubleSide
    } );
    spriteMaterial.transparent = true;

    let sprite = new THREE.Sprite(spriteMaterial);
    sprite.center = new THREE.Vector2(0.5, 0.5);
    let poi = {w: canvas.width, h: canvas.height};
    sprite.scale.set(poi.w/window.innerHeight, poi.h/window.innerHeight, 1.0);
    return sprite;
  }

  let twoPi = Math.PI * 2;
  let gui = new dat.GUI();
  let scene = new THREE.Scene();
  scene.background = new THREE.Color( 0x444444 );
  scene.add(new THREE.AxesHelper(200));

  let boxGeo = new THREE.BoxBufferGeometry(5, 5, 5);
  let boxMesh = new THREE.Mesh(boxGeo, new THREE.MeshBasicMaterial({color:"#6e8989", wireframe: true}));
  boxMesh.position.set(7, 10, 0);
  scene.add(boxMesh);

  let canvas = generateCanvas("你好啊");
  let sprite = makeCanvasSprite(canvas);
  scene.add(sprite);

  let camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 50 );
  camera.position.z = 30;

  let renderer = new THREE.WebGLRenderer( { antialias: true } );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  document.body.appendChild( renderer.domElement );

  let orbit = new THREE.OrbitControls( camera, renderer.domElement );
  // orbit.enableZoom = false;

  let render = function () {

    requestAnimationFrame( render );
    renderer.render( scene, camera );
  };

  window.addEventListener( 'resize', function () {

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize( window.innerWidth, window.innerHeight );

  }, false );

  render();


</script>
</body>
</html>
/*
* https://github.com/dy/bitmap-sdf
* Calculate SDF for image/bitmap/bw data
* This project is a fork of MapBox's TinySDF that works directly on an input Canvas instead of generating the glyphs themselves.
*/

    var INF = 1e20;

    function clamp(value, min, max) {
        return min < max
            ? (value < min ? min : value > max ? max : value)
            : (value < max ? max : value > min ? min : value)
    }

    function calcSDF(src, options) {
        if (!options) options = {}

        var cutoff = options.cutoff == null ? 0.25 : options.cutoff
        var radius = options.radius == null ? 8 : options.radius
        var channel = options.channel || 0
        var w, h, size, data, intData, stride, ctx, canvas, imgData, i, l

        // handle image container
        if (ArrayBuffer.isView(src) || Array.isArray(src)) {
            if (!options.width || !options.height) throw Error('For raw data width and height should be provided by options')
            w = options.width, h = options.height
            data = src

            if (!options.stride) stride = Math.floor(src.length / w / h)
            else stride = options.stride
        }
        else {
            if (window.HTMLCanvasElement && src instanceof window.HTMLCanvasElement) {
                canvas = src
                ctx = canvas.getContext('2d')
                w = canvas.width, h = canvas.height
                imgData = ctx.getImageData(0, 0, w, h)
                data = imgData.data
                stride = 4
            }
            else if (window.CanvasRenderingContext2D && src instanceof window.CanvasRenderingContext2D) {
                canvas = src.canvas
                ctx = src
                w = canvas.width, h = canvas.height
                imgData = ctx.getImageData(0, 0, w, h)
                data = imgData.data
                stride = 4
            }
            else if (window.ImageData && src instanceof window.ImageData) {
                imgData = src
                w = src.width, h = src.height
                data = imgData.data
                stride = 4
            }
        }

        size = Math.max(w, h)

        //convert int data to floats
        if ((window.Uint8ClampedArray && data instanceof window.Uint8ClampedArray) || (window.Uint8Array && data instanceof window.Uint8Array)) {
            intData = data
            data = Array(w * h)

            for (i = 0, l = intData.length; i < l; i++) {
                data[i] = intData[i * stride + channel] / 255
            }
        }
        else {
            if (stride !== 1) throw Error('Raw data can have only 1 value per pixel')
        }

        // temporary arrays for the distance transform
        var gridOuter = Array(w * h)
        var gridInner = Array(w * h)
        var f = Array(size)
        var d = Array(size)
        var z = Array(size + 1)
        var v = Array(size)

        for (i = 0, l = w * h; i < l; i++) {
            var a = data[i]
            gridOuter[i] = a === 1 ? 0 : a === 0 ? INF : Math.pow(Math.max(0, 0.5 - a), 2)
            gridInner[i] = a === 1 ? INF : a === 0 ? 0 : Math.pow(Math.max(0, a - 0.5), 2)
        }

        edt(gridOuter, w, h, f, d, v, z)
        edt(gridInner, w, h, f, d, v, z)

        var dist = window.Float32Array ? new Float32Array(w * h) : new Array(w * h)

        for (i = 0, l = w * h; i < l; i++) {
            dist[i] = clamp(1 - ((gridOuter[i] - gridInner[i]) / radius + cutoff), 0, 1)
        }

        return dist
    }

    // 2D Euclidean distance transform by Felzenszwalb & Huttenlocher https://cs.brown.edu/~pff/dt/
    function edt(data, width, height, f, d, v, z) {
        for (var x = 0; x < width; x++) {
            for (var y = 0; y < height; y++) {
                f[y] = data[y * width + x]
            }
            edt1d(f, d, v, z, height)
            for (y = 0; y < height; y++) {
                data[y * width + x] = d[y]
            }
        }
        for (y = 0; y < height; y++) {
            for (x = 0; x < width; x++) {
                f[x] = data[y * width + x]
            }
            edt1d(f, d, v, z, width)
            for (x = 0; x < width; x++) {
                data[y * width + x] = Math.sqrt(d[x])
            }
        }
    }

    // 1D squared distance transform
    function edt1d(f, d, v, z, n) {
        v[0] = 0;
        z[0] = -INF
        z[1] = +INF

        for (var q = 1, k = 0; q < n; q++) {
            var s = ((f[q] + q * q) - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k])
            while (s <= z[k]) {
                k--
                s = ((f[q] + q * q) - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k])
            }
            k++
            v[k] = q
            z[k] = s
            z[k + 1] = +INF
        }

        for (q = 0, k = 0; q < n; q++) {
            while (z[k + 1] < q) k++
            d[q] = (q - v[k]) * (q - v[k]) + f[v[k]]
        }
    }

function removeRags(strLst) {
    let arr = strLst.split("");
    let map = {}, keys = [];
    for (let i = 0; i < arr.length; i++) {
      let str = arr[i];
      if (map[str] == undefined) {
        map[str] = [i];
      } else {
        map[str].push(i);
        if (keys.indexOf(str) < 0) {
          keys.push(str);
        }
      }
    }
    for (let i = keys.length - 1; i > -1; i--) {
      let key = keys[i];
      let start = map[key][0] + 1, deleteCount = map[key][1] - map[key][0];
      if (arr[start - 1] == key) {
        arr.splice(start, deleteCount);
      }
    }
    return arr.join("");
  }

Cesium 中的字体轮廓,《Labels.html》

function setFont() {
  Sandcastle.declare(setFont);
  viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222),
    label: {
      text: "模型1",
      font: "16px Helvetica",
      fillColor: Cesium.Color.WHITE,
      outlineColor: Cesium.Color.BLACK,
      outlineWidth: 4,
      style: Cesium.LabelStyle.FILL_AND_OUTLINE,
    },
  });
}

outlineWidth 是如何传入shader的
LabelCollection.update() => BillboardCollection.update()
BillboardCollection function createVAF()

BillboardCollection var attributeLocationsBatched = {}
BillboardCollection function writeSDF()

VertexArrayFacade._verifyAttributes()
VertexArray gl.vertexAttribPointer //指定了渲染时索引值为 index 的顶点属性数组的数据格式和位置。

BillboardCollectionVS.glsl

attribute vec2 sdf;        // sdf outline color (rgb) and width (w)
#ifdef SDF
    vec4 outlineColor;
    float outlineWidth;

    temp = sdf.x;
    temp = temp * SHIFT_RIGHT8;
    outlineColor.b = (temp - floor(temp)) * SHIFT_LEFT8;
    temp = floor(temp) * SHIFT_RIGHT8;
    outlineColor.g = (temp - floor(temp)) * SHIFT_LEFT8;
    outlineColor.r = floor(temp);

    temp = sdf.y;
    temp = temp * SHIFT_RIGHT8;
    float temp3 = (temp - floor(temp)) * SHIFT_LEFT8;
    temp = floor(temp) * SHIFT_RIGHT8;
    outlineWidth = (temp - floor(temp)) * SHIFT_LEFT8;
    outlineColor.a = floor(temp);
    outlineColor /= 255.0;

    v_outlineWidth = outlineWidth / 255.0;
    v_outlineColor = outlineColor;
    v_outlineColor.a *= translucency;
#endif
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>chapter1.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 pos;
  void main() {
    pos = (vec2(position) + 1.0) * 0.5;
    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 pos;

  // Plot a line on Y using a value between 0.0-1.0
  float plot(vec2 st) {
    return smoothstep(0.02, 0.000, abs(st.y - st.x));
  }

  void main() {
    float y = pos.x;

    vec3 color = vec3(y);

    // Plot a line
    float pct = plot(pos);
    color = (1.0 - pct) * color + pct * vec3(0.0, 1.0, 0.0);

    gl_FragColor = vec4(color, 1.0);
  }
</script>
<script>
  //https://thebookofshaders.com/05/?lan=ch
  var container;
  var camera, scene, renderer;
  var uniforms;

  init();
  animate();

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

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

    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) }
    };

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

    var 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.05;
    renderer.render( scene, camera );
  }
</script>
</body>
</html>

https://www.w3h5.com/post/450.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使用ShaderMaterial实现水晶球效果,需要先创建一个球体网格,然后创建一个自定义的着色器,最后将着色器应用到材质上。 以下是一个基本的实现示例: 1. 创建一个球体网格 ```javascript const geometry = new THREE.SphereGeometry(5, 32, 32); const material = new THREE.MeshBasicMaterial({ color: 0xffffff }); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh); ``` 2. 创建着色器 ```javascript const vertexShader = ` varying vec3 vNormal; varying vec3 vPosition; void main() { vNormal = normal; vPosition = position; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `; const fragmentShader = ` varying vec3 vNormal; varying vec3 vPosition; void main() { vec3 lightPosition = vec3(0.0, 0.0, 10.0); vec3 lightDirection = normalize(lightPosition - vPosition); float diffuse = max(dot(vNormal, lightDirection), 0.0); gl_FragColor = vec4(vec3(diffuse), 1.0); } `; const uniforms = {}; const shaderMaterial = new THREE.ShaderMaterial({ uniforms, vertexShader, fragmentShader, }); ``` 上面的着色器代码使用了基本的光照计算,将球体的各个面根据光照情况渲染成不同的颜色。你可以根据需要修改着色器代码。 3. 应用材质 ```javascript mesh.material = shaderMaterial; ``` 现在,你就可以看到球体以水晶般的效果呈现在屏幕上了。 需要注意的是,由于使用了自定义的着色器,性能可能会受到影响,因此在实际应用中,应该根据需要进行优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值