着色器:如何在不生成几何体的情况下绘制 3D 点顶点?

Shaders: How to draw 3D point verts without generating geometry?

我有一个 3D Webgl 场景。我正在使用 Regl http://regl.party/ 。这是 WebGL。所以我基本上是在直接编写 GLSL。

这是一个游戏项目。我有一组 3D 位置 [[x,y,z] ...],它们是子弹或射弹。我想将这些子弹绘制为简单的立方体、球体或粒子。外观无要求

如何制作着色器和绘制调用,而不必为子弹创建一组重复的几何图形?

更喜欢带有 vert 和 frag 着色器示例的答案,该示例演示了预期的数据输入并且可以通过逆向工程来处理 CPU 绑定层

您可以将所有子弹绘制为点精灵,在这种情况下,您只需提供每颗子弹的位置和大小并将它们绘制为 GL_POINTS。每个“点”都根据顶点着色器的输出(每个点运行一次)光栅化为一个正方形。您的片段着色器会为该正方形中的每个片段调用,并且可以根据需要为片段着色——使用平面颜色、通过对纹理进行采样,或者您想要的任何其他方式。

或者您可以为所有项目符号提供一个模型,为每个项目符号提供一个单独的转换,然后将它们绘制为 GL_TRIANGLESGL_TRIANGLE_STRIP 或其他任何实例。阅读 the OpenGL wiki.

上的实例化

不是 WebGL 编码员,所以带着偏见阅读...

  1. 编码纹理中的顶点

    小心使用不像 GL_LUMINANCE32F_ARB 那样夹紧到 <0.0,+1.0> 的纹理格式或仅使用该范围内的顶点。要检查是否夹紧,请使用:

  2. 渲染覆盖整个屏幕的单个矩形

    并使用来自#1的纹理作为输入。这将确保为 screen/view 的每个像素调用片段着色器恰好一次。

  3. 片段着色器内部读取纹理并检查片段到顶点的距离

    基于它渲染你的东西或 dicard() 片段...球体很容易,但是基于顶点的距离渲染盒子和其他形状可能很复杂,特别是如果它们可以任意定向(哪个需要输入纹理中的附加信息)。

    为了缓解这种情况,您可以将它们预渲染到一些纹理中并将距离用作纹理坐标...

我的这个回答就是用的这个技巧:

  • raytrace through 3D mesh

您有时可以通过将 GL_POINTS 与大型 gl_PointSize 和自定义片段着色器一起使用来逃脱。 此处显示的示例使用片段 alpha 的点中心距离。 (您也可以对纹理进行采样)

虽然对大尺寸的支持可能有限,因此在决定此路线之前请检查一下。

var canvas = document.getElementById('cvs');
gl = canvas.getContext('webgl'); 

var vertices = [
  -0.5, 0.75,0.0,
   0.0, 0.5, 0.0,
  -0.75,0.25,0.0, 
];

var vertex_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);

var vertCode =
  `attribute vec3 coord;
   void main(void) {
     gl_Position = vec4(coord, 1.0);
     gl_PointSize = 50.0;
   }`;

var vertShader = gl.createShader(gl.VERTEX_SHADER);  
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);

var fragCode =
  `void main(void) {
     mediump float ds = distance(gl_PointCoord.xy, vec2(0.5,0.5))*2.0;
     mediump vec4 fg_color=vec4(0.0, 0.0, 0.0,1.0- ds);     
     gl_FragColor = fg_color;
  }`;
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, fragCode);
gl.compileShader(fragShader);

var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertShader); 
gl.attachShader(shaderProgram, fragShader);


gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);

gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);

var coord = gl.getAttribLocation(shaderProgram, "coord");

gl.vertexAttribPointer(coord, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(coord);

gl.viewport(0,0,canvas.width,canvas.height);
gl.drawArrays(gl.POINTS, 0, 3);
<!doctype html>
<html>
   <body>
      <canvas width = "400" height = "400" id = "cvs"></canvas>
   </body>
</html>

你创建了一个封装了一堆数据的regl命令。然后你可以用一个对象来调用它。

每个制服都可以使用一个可选函数来提供它的值。该函数传递了一个 regl 上下文作为第一个参数,然后将您传递的对象作为第二个参数传递,因此您可以使用不同的对象多次调用它以在其他地方绘制相同的东西(相同的顶点,相同的着色器)。

var regl = createREGL()

const objects = [];
const numObjects = 100;
for (let i = 0; i < numObjects; ++i) {
  objects.push({
    x: rand(-1, 1),
    y: rand(-1, 1),
    speed: rand(.5, 1.5),
    direction: rand(0, Math.PI * 2),
    color: [rand(0, 1), rand(0, 1), rand(0, 1), 1],
  });
}

function rand(min, max) {
  return Math.random() * (max - min) + min;
}

const starPositions = [[0, 0, 0]];
const starElements = [];
const numPoints = 5;
for (let i = 0; i < numPoints; ++i) {
  for (let j = 0; j < 2; ++j) {
    const a = (i * 2 + j) / (numPoints * 2) * Math.PI * 2;
    const r = 0.5 + j * 0.5;
    starPositions.push([
      Math.sin(a) * r,
      Math.cos(a) * r,
      0,
    ]);
  }
  starElements.push([
    0, 1 + i * 2, 1 + i * 2 + 1,
  ]);
}

const drawStar = regl({
  frag: `
  precision mediump float;
  uniform vec4 color;
  void main () {
    gl_FragColor = color;
  }`,
  vert: `
  precision mediump float;
  attribute vec3 position;
  uniform mat4 mat;
  void main() {
    gl_Position = mat * vec4(position, 1);
  }`,
  attributes: {
    position: starPositions,
  },
  elements: starElements,
  uniforms: {
    mat: (ctx, props) => {
      const {viewportWidth, viewportHeight} = ctx;
      const {x, y} = props;
      const aspect = viewportWidth / viewportHeight;
      return [.1 / aspect, 0, 0, 0,
              0, .1, 0, 0,
              0, 0, 0, 0,
              x, y, 0, 1];
    },
    color: (ctx, props) => props.color,
  }
})

regl.frame(function () {
  regl.clear({
    color: [0, 0, 0, 1]
  });
  objects.forEach((o) => {
    o.direction += rand(-0.1, 0.1);
    o.x += Math.cos(o.direction) * o.speed * 0.01;
    o.y += Math.sin(o.direction) * o.speed * 0.01;
    o.x  = (o.x + 3) % 2 - 1;
    o.y  = (o.y + 3) % 2 - 1;
    drawStar(o);
  });
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/regl/1.3.11/regl.min.js"></script>