Shader 和 Pass

SpriteJSNext在WebGL/WebGL2模式下,可以支持自定义Shader,这样我们就可以创建自定义的渲染模式。

比如我们要想把Path元素的背景填充修改为虚线填充,我们可以自定义Shader实现:

  1. const vertex = `
  2. attribute vec3 a_vertexPosition;
  3. attribute vec4 a_color;
  4. varying vec4 vColor;
  5. void main() {
  6. gl_PointSize = 1.0;
  7. gl_Position = vec4(a_vertexPosition.xy, 1.0, 1.0);
  8. vColor = a_color;
  9. }
  10. `;
  11. const fragment = `
  12. precision mediump float;
  13. uniform vec4 u_color;
  14. uniform vec2 u_resolution;
  15. varying vec4 vColor;
  16. void main() {
  17. vec2 st = gl_FragCoord.xy / u_resolution;
  18. float d = (st.x - st.y) / sqrt(2.0);
  19. d = fract(d * 50.0);
  20. d = 1.0 - abs(d);
  21. d = smoothstep(0.3, 0.4, d) - smoothstep(0.8, 0.9, d);
  22. gl_FragColor = d * vColor;
  23. }
  24. `;
  25. const container = document.getElementById('stage');
  26. const scene = new spritejs.Scene({
  27. container,
  28. width: 600,
  29. height: 600,
  30. });
  31. const fglayer = scene.layer('fglayer');
  32. document.querySelector('#stage canvas').style.backgroundColor = '#eee';
  33. const program = fglayer.renderer.createProgram({vertex, fragment});
  34. const s = new spritejs.Ellipse();
  35. s.attr({
  36. radius: [100, 50],
  37. pos: [300, 300],
  38. fillColor: 'blue',
  39. });
  40. s.setProgram(program);
  41. const {width, height} = fglayer.getResolution();
  42. s.setUniforms({
  43. u_resolution: [width, height],
  44. });
  45. fglayer.append(s);

还可以创建更复杂的Shader:

  1. const vertex = `
  2. attribute vec3 a_vertexPosition;
  3. // attribute vec4 a_color;
  4. // varying vec4 vColor;
  5. void main() {
  6. gl_PointSize = 1.0;
  7. gl_Position = vec4(a_vertexPosition.xy, 1.0, 1.0);
  8. // vColor = a_color;
  9. }
  10. `;
  11. const fragment = `
  12. precision mediump float;
  13. highp float random(vec2 co)
  14. {
  15. highp float a = 12.9898;
  16. highp float b = 78.233;
  17. highp float c = 43758.5453;
  18. highp float dt= dot(co.xy ,vec2(a,b));
  19. highp float sn= mod(dt,3.14);
  20. return fract(sin(sn) * c);
  21. }
  22. // Value Noise by Inigo Quilez - iq/2013
  23. // https://www.shadertoy.com/view/lsf3WH
  24. highp float noise(vec2 st) {
  25. vec2 i = floor(st);
  26. vec2 f = fract(st);
  27. vec2 u = f * f * (3.0 - 2.0 * f);
  28. return mix( mix( random( i + vec2(0.0,0.0) ),
  29. random( i + vec2(1.0,0.0) ), u.x),
  30. mix( random( i + vec2(0.0,1.0) ),
  31. random( i + vec2(1.0,1.0) ), u.x), u.y);
  32. }
  33. #ifndef OCTAVES
  34. #define OCTAVES 6
  35. #endif
  36. float mist(vec2 st) {
  37. //Initial values
  38. float value = 0.0;
  39. float amplitude = 0.5;
  40. float frequency = 0.0;
  41. // Loop of octaves
  42. for(int i = 0; i < OCTAVES; i++) {
  43. value += amplitude * noise(st);
  44. st *= 2.0;
  45. amplitude *= 0.5;
  46. }
  47. return value;
  48. }
  49. // Function from Iñigo Quiles
  50. // https://www.shadertoy.com/view/MsS3Wc
  51. vec3 hsb2rgb(vec3 c){
  52. vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0), 6.0)-3.0)-1.0, 0.0, 1.0);
  53. rgb = rgb * rgb * (3.0 - 2.0 * rgb);
  54. return c.z * mix(vec3(1.0), rgb, c.y);
  55. }
  56. uniform float u_time;
  57. uniform vec2 u_resolution;
  58. void main() {
  59. vec2 st = gl_FragCoord.xy / u_resolution;
  60. st.x += 0.1 * u_time;
  61. gl_FragColor = vec4(hsb2rgb(vec3(mist(st), 1.0, 1.0)),1.0);
  62. }
  63. `;
  64. const birdsJsonUrl = 'https://s5.ssl.qhres.com/static/5f6911b7b91c88da.json';
  65. const birdsRes = 'https://p.ssl.qhimg.com/d/inn/c886d09f/birds.png';
  66. (async function () {
  67. const container = document.getElementById('stage');
  68. const scene = new spritejs.Scene({
  69. container,
  70. // displayRatio: 2,
  71. width: 600,
  72. height: 600,
  73. // mode: 'stickyHeight',
  74. // contextType: '2d',
  75. });
  76. const fglayer = scene.layer('fglayer', {autoRender: false});
  77. await scene.preload([birdsRes, birdsJsonUrl]);
  78. const program = fglayer.renderer.createProgram({vertex, fragment});
  79. const sky = new spritejs.Block();
  80. sky.attr({
  81. size: [600, 600],
  82. });
  83. sky.setProgram(program);
  84. const {width, height} = fglayer.getResolution();
  85. sky.setUniforms({
  86. u_time: 0,
  87. u_resolution: [width, height],
  88. });
  89. sky.setShaderAttribute('a_pp', () => {
  90. return [Math.random(), Math.random(), Math.random()];
  91. });
  92. fglayer.append(sky);
  93. const bird = new spritejs.Sprite('bird1.png');
  94. bird.attr({
  95. anchor: 0.5,
  96. pos: [300, 300],
  97. scale: 0.5,
  98. });
  99. fglayer.append(bird);
  100. let idx = 0;
  101. setInterval(() => {
  102. // bird.forceUpdate();
  103. bird.attributes.texture = `bird${++idx % 3 + 1}.png`;
  104. }, 100);
  105. requestAnimationFrame(function update(t) {
  106. sky.setUniforms({
  107. u_time: t / 1000,
  108. });
  109. fglayer.render();
  110. requestAnimationFrame(update);
  111. });
  112. }());

上面的代码通过shader创建的云层和地面。

后期处理通道

3.2之后的版本中,我们还可以将layer渲染的结果送入后期处理通道进行处理:

  1. const {Scene, Sprite} = spritejs;
  2. const container = document.getElementById('stage');
  3. const scene = new Scene({
  4. container,
  5. width: 1200,
  6. height: 1200,
  7. });
  8. const layer = scene.layer('fglayer');
  9. for(let i = 0; i < 10; i++) {
  10. for(let j = 0; j < 10; j++) {
  11. const s = new Sprite({
  12. width: 100,
  13. height: 100,
  14. x: i * 120,
  15. y: j * 120,
  16. bgcolor: `hsl(${Math.random() * 360}, 50%, 50%)`,
  17. });
  18. layer.append(s);
  19. }
  20. }
  21. const fragment = `precision mediump float;
  22. varying vec3 vTextureCoord;
  23. uniform sampler2D u_texSampler;
  24. uniform vec2 u_mousePos;
  25. uniform vec2 u_resolution;
  26. void main() {
  27. vec2 st = gl_FragCoord.xy / u_resolution;
  28. st.y = 1.0 - st.y;
  29. float d = distance(st, u_mousePos);
  30. vec4 color = texture2D(u_texSampler, vTextureCoord.xy);
  31. color.a *= mix(0.1, 1.0, (1.0 - smoothstep(0.0, 0.2, d)));
  32. color.rgb *= 1.0 - step(0.2, d);
  33. gl_FragColor = color;
  34. }
  35. `;
  36. const {width, height} = layer.getResolution();
  37. layer.addPass({fragment,
  38. uniforms: {
  39. u_resolution: [width, height],
  40. u_mousePos: [0.1, 0.1],
  41. }});
  42. layer.addEventListener('mousemove', (e) => {
  43. // const {width, height} = layer.getResolution();
  44. const x = e.x / width,
  45. y = e.y / height;
  46. layer.pass[0].setUniforms({
  47. u_mousePos: [x, y],
  48. });
  49. });
  50. layer.tick();

这样我们就可以方便灵活地在图层上实现各种视觉特效和交互特效了。