首页 > 解决方案 > 人们如何将函数传递给 OpenGL ES GLSL 函数?

问题描述

我想旋转和平移一个用有符号距离函数制作的二维形状。文档说这是方法:

vec3 opTx( in vec3 p, in transform t, in sdf3d primitive )
{
    return primitive( invert(t)*p );
}

在我看来,primitive我可以调用某种函数(或结构),有没有办法传递这样的函数(或者这有什么意义)?

首先我不知道什么transformsdf3d类型是什么,invert功能是什么。其次,我如何将其应用于 2d?

const fShaderSource = `#version 300 es

precision mediump float;

uniform vec2 u_resolution;

out vec4 outColor;

float sdLine( 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 );
}

vec2 screenToWorld(vec2 screen) {
  vec2 result = 2.0 * (screen/u_resolution.xy - 0.5);
  result.x *= u_resolution.x/u_resolution.y;
  return result;
}

void main() {

  vec2 p = screenToWorld(gl_FragCoord.xy);
  
  float sd = sdLine(p, vec2(0.0), vec2(0.0, 0.5));
  
  vec3 col = vec3(0.0);
  
  col += 1.0 - smoothstep(0.0, 0.04, abs(sd));

  outColor = vec4(col, 1.0);
}

`;

const vShaderSource = `#version 300 es

precision mediump float;

in vec2 a_position;

uniform vec2 u_resolution;

void main() {
  gl_Position = vec4(a_position, 0, 1);
}
`;

main(document.getElementById('app'));

function main(element) {
  
  const canvas = document.createElement('canvas'),
        gl = canvas.getContext('webgl2');
  element.append(canvas);
  const displayWidth = canvas.clientWidth,
        displayHeight = canvas.clientHeight;
  canvas.width = displayWidth;
  canvas.height = displayHeight;


  let graphics = new Graphics({width: displayWidth, height: displayHeight}, gl);
  
  new Loop(() => {
     graphics.render();
  }).start();
}

function Graphics(state, gl) {

  const { width, height } = state;

  let vShader = createShader(gl, gl.VERTEX_SHADER, vShaderSource);
  let fShader = createShader(gl, gl.FRAGMENT_SHADER, fShaderSource);

  let program = createProgram(gl, vShader, fShader);

  let posAttrLocation = gl.getAttribLocation(program, "a_position");
  let posBuffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);


  /*
    (-1, 1).( 1, 1)
        .
    (-1,-1).( 1,-1)
   */
  let positions = [
    -1, 1,
    -1, -1,
    1, -1,
    -1, 1,
    1,-1,
    1, 1
  ];

  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);


  let vao = gl.createVertexArray();
  gl.bindVertexArray(vao);

  gl.enableVertexAttribArray(posAttrLocation);

  let size = 2,
      type = gl.FLOAT,
      normalize = false,
      stride = 0,
      offset = 0;

  gl.vertexAttribPointer(posAttrLocation,
                         size,
                         type,
                         normalize,
                         stride,
                         offset);



  let resUniformLocation = gl.getUniformLocation(program, "u_resolution");




  gl.clearColor(0, 0, 0, 0);

  this.render = () => {
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.useProgram(program);

    gl.uniform2f(resUniformLocation, gl.canvas.width, gl.canvas.height);

    gl.bindVertexArray(vao);
    gl.drawArrays(gl.TRIANGLES, 0, 6);


  };

}

function createShader(gl, type, source) {
  let shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);

  if (success) {
    return shader;
  }

  console.error(gl.getShaderInfoLog(shader));
  gl.deleteShader(shader);
  return null;
};

function createProgram(gl, vShader, fShader) {
  let program = gl.createProgram();
  gl.attachShader(program, vShader);
  gl.attachShader(program, fShader);
  gl.linkProgram(program);
  let success = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) {
    return program;
  }

  console.error(gl.getProgramInfoLog(program));
  gl.deleteProgram(program);
  return null;
}


// Loop Library
function Loop(fn) {

const perf = window.performance !== undefined ? window.performance : Date;

const now = () => perf.now();

const raf = window.requestAnimationFrame;

  let running = false,
      lastUpdate = now(),
      frame = 0;

  this.start = () => {
    if (running) {
      return this;
    }

    running = true;
    lastUpdate = now();
    frame = raf(tick);
    return this;
  };

  this.stop = () => {
    running = false;

    if (frame != 0) {
      raf.cancel(frame);
    }

    frame = 0;
    return this;
  };

  const tick = () => {
    frame = raf(tick);
    const time = now();
    const dt = time - lastUpdate;
    fn(dt);
    lastUpdate = time;
  };
}
#app canvas {
  position: fixed;
  top: 50%;
  bottom: 0;
  left: 50%;
  right: 0;

  width: 100vmin;
  height: 70vmin;

  transform: translate(-50%, -25%);

  image-rendering: optimizeSpeed;
  cursor: none;
  margin: auto;
}
<div id="app">
</div>

标签: javascriptwebgl2

解决方案


GLSL 不允许您将函数作为参数传递。您链接的代码段更像是一个宏,您应该在其中手动内联primitive.

就在您复制粘贴的代码上方,transform说明了 的定义:

下面的代码假定 transform 仅编码旋转和平移(例如,作为 3x4 矩阵,或作为四元数和向量),并且其中不包含任何缩放因子。

要在 2D 中工作,您使用 3x3 矩阵,其中上方的 2x2 矩阵编码旋转,底部行的前两列编码平移。

把它们放在一起:(用这个替换https://www.shadertoy.com/view/MldcD7的mainImage函数)

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y;
    p *= 1.1;

    // iFrame is a uniform that shadertoy provides: the current frame number
    float angle = float(iFrame) / 60.0;

    // Rotation part: rotate by `angle`, or once every 60 fps.
    // Translation part: Move across the screen left to right, every 60 fps.
    mat3 transform = mat3(
        cos(angle), sin(angle), 0.0,
        -sin(angle),cos(angle), 0.0,
        (float(iFrame % 60)/60.0 - 0.5) * 2.0, 0.0, 1.0
        );

    vec2 tri = vec2(0.3,-1.1); // width, height

    // Here, we first apply the inverse transform to our input, then pass the resulting point to our primitive, here sdTriangleIsosceles
    float d = sdTriangleIsosceles( tri, (inverse(transform) * vec3(p, 1.0)).xy );

    vec3 col = vec3(1.0) - sign(d)*vec3(0.1,0.4,0.7);
    col *= 1.0 - exp(-2.0*abs(d));
    col *= 0.8 + 0.2*cos(140.0*d);
    col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.02,abs(d)) );

    fragColor = vec4(col*1.2,1.0);
}

推荐阅读