Шейдеры: как рисовать точечные 3D-вершины без генерации геометрии? - PullRequest
0 голосов
/ 12 января 2019

У меня есть 3D сцена Webgl. Я использую Regl http://regl.party/. Который является WebGL. По сути, я пишу прямо GLSL.

Это игровой проект. У меня есть массив трехмерных позиций [[x, y, z] ...], которые являются пулями или снарядами. Я хочу нарисовать эти пули в виде простого куба, сферы или частицы. Нет требований к внешнему виду.

Как сделать так, чтобы шейдеры и рисование вызывали это, не создавая повторяющийся дублирующий набор геометрии для пуль?

Предпочитая ответ с примером vert и фраг-шейдера, который демонстрирует ожидаемый ввод данных и может быть подвергнут обратной обработке для обработки уровня связывания ЦП

Ответы [ 4 ]

0 голосов
/ 13 января 2019

Вы создаете команду 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>
0 голосов
/ 12 января 2019

Не кодер WebGL, поэтому читайте с предубеждением ...

  1. Кодирование вершин в текстуре

    Остерегайтесь ограничения, используйте формат текстуры, который не ограничивает <0.0,+1.0>, как GL_LUMINANCE32F_ARB, или используйте вершины только в этом диапазоне. Для проверки зажима используйте:

  2. Отображение одного прямоугольника, охватывающего весь экран

    и используйте текстуру # 1 в качестве входных данных. Это гарантирует, что фрагментный шейдер вызывается для каждого пикселя экрана / вида ровно один раз.

  3. Внутри фрагментного шейдера прочитайте текстуру и проверьте расстояние фрагмента до ваших вершин

    основанный на этом рендеринг вашего материала или dicard() фрагмента ... сферы просты, но коробки и другие формы могут быть сложны для визуализации на основе расстояния вершины, особенно если они могут быть произвольно ориентированы (которые нуждаются в дополнительной информации в входная текстура).

    Чтобы упростить это, вы можете превратить их в некоторую текстуру и использовать расстояние в качестве координат текстуры ...

Этот мой ответ использует эту технику:

0 голосов
/ 13 января 2019

Иногда вы можете использовать GL_POINTS с большим gl_PointSize и индивидуальным фрагментным шейдером. Здесь показан пример использования расстояния до центра точки для фрагмента альфа. (Вы также можете попробовать текстуру)

Однако поддержка больших размеров точек может быть ограничена, поэтому проверьте это перед принятием решения об этом маршруте.

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>
0 голосов
/ 12 января 2019

Вы можете нарисовать все пули как точечные спрайты, в этом случае вам просто нужно указать положение и размер каждой пули и нарисовать их как GL_POINTS. Каждая «точка» растеризуется в квадрат на основе выходных данных вашего вершинного шейдера (который запускается один раз для каждой точки). Ваш фрагментный шейдер вызывается для каждого фрагмента в этом квадрате и может окрашивать фрагмент по своему усмотрению - плоским цветом, сэмплированием текстуры или любым другим способом.

Или вы можете предоставить одну модель для всех пуль, отдельное преобразование для каждой пули и нарисовать их как экземпляры GL_TRIANGLES или GL_TRIANGLE_STRIP или что-то еще. Прочтите об экземпляре на вики OpenGL .

...