Масштабировать объекты BufferGeometry от их центра

У меня есть большое количество случайно размещенных кругов, хранящихся в одной буферной геометрии. Я пытаюсь масштабировать каждый из них на основе аудиовхода из их центров, но они продолжают масштабироваться относительно центра сцены. Вместо того, чтобы увеличивать радиусы и оставаться неподвижными, они увеличиваются, но при этом удаляются от центра.

uniform float scale;
varying vec3 color;
void main() 
{

  mat4 sPos = mat4(vec4(scale,0.0,0.0,0.0),
               vec4(0.0,scale,0.0,0.0),
               vec4(0.0,0.0,1.0,0.0),
               vec4(0.0,0.0,0.0,1.0));
  vec3 pos = position * normal;
  vec4 modelViewPosition = modelViewMatrix * sPos * vec4(pos, 1.0) ;

  gl_Position = projectionMatrix * modelViewPosition;
}

Это то, что я пытаюсь. Существует ли матричное преобразование, которое может переместить начало координат в центр каждого круга, не испортив другие аспекты сцены?

малое значение масштаба маленькое значение масштаба

большее значение масштаба большее значение масштаба


person N. Pyle    schedule 10.08.2017    source источник
comment
(Поскольку у вас есть тег three.js, это комментарий исключительно с точки зрения three.js.) Вы используете один буфер, потому что несколько сеток ухудшают производительность? Рассматривали ли вы вместо этого использование экземпляров (InstancedBufferGeometry)?   -  person TheJim01    schedule 10.08.2017
comment
Да, частота кадров довольно быстро падала при использовании круговой буферизации (~ 100-250 меш). Я не рассматривал его использование, но я также не знаком с ним. Разумно ли иметь ~ 3000–5000 экземпляров и поддерживать 60 кадров в секунду?   -  person N. Pyle    schedule 10.08.2017
comment
Дополнительный вопрос к вашему circleBufferGeometry комментарию: вы повторно использовали объект геометрии или создавали новый circleBufferGeometry для каждого меша? Я не могу предположить, какой прирост производительности вы получите, используя инстансы. Вы потеряете эффективность одного вызова отрисовки (одного меша), но получите простоту использования нескольких мешей.   -  person TheJim01    schedule 10.08.2017


Ответы (1)


Добавьте нормальную * шкалу

uniform float scale;
varying vec3 color;
void main() 
{
  vec3 pos = position + normal * scale;
  vec4 modelViewPosition = modelViewMatrix * vec4(pos, 1.0) ;

  gl_Position = projectionMatrix * modelViewPosition;
}

Пример:

const vs = `
uniform float scale;
void main() 
{
  vec3 pos = position + normal * scale;
  vec4 modelViewPosition = modelViewMatrix * vec4(pos, 1.0) ;
  gl_Position = projectionMatrix * modelViewPosition;
}
`;

const fs = `
void main() {
  gl_FragColor = vec4(1,0,0,1);
}
`;

const camera = new THREE.PerspectiveCamera(40, 1, 1, 1000);
camera.position.z = 400;

const scene = new THREE.Scene();

const uniforms = {
  scale: { value: 1 },
};

var shaderMaterial = new THREE.ShaderMaterial({
  uniforms:       uniforms,
  vertexShader:   vs,
  fragmentShader: fs,
});

const geometry = new THREE.BufferGeometry();
const numCircles = 20;
const numEdgePointsPerCircle = 36;
const numPointsPerCircle = numEdgePointsPerCircle * 3;
const totalPoints = numPointsPerCircle * numCircles;
const positions = new Float32Array(totalPoints * 3);
const normals = new Float32Array(totalPoints * 3);

const radius = 10;
let off = 0;

for (let c = 0; c < numCircles; ++c) {
  const cx = rand(-100, 100);
  const cy = rand(-100, 100);
  for (let p = 0; p < numEdgePointsPerCircle; ++p) {
    const a0 = (p + 0) * Math.PI * 2 / numEdgePointsPerCircle;
    const a1 = (p + 1) * Math.PI * 2 / numEdgePointsPerCircle;
    positions[off + 0] = cx;
    positions[off + 1] = cy;
    positions[off + 2] = 0;
    positions[off + 3] = cx + Math.cos(a0) * radius;
    positions[off + 4] = cy + Math.sin(a0) * radius;
    positions[off + 5] = 0;
    positions[off + 6] = cx + Math.cos(a1) * radius;
    positions[off + 7] = cy + Math.sin(a1) * radius;
    positions[off + 8] = 0;
    normals[off + 0] = 0;        
    normals[off + 1] = 0;        
    normals[off + 2] = 0;
    normals[off + 3] = Math.cos(a0);        
    normals[off + 4] = Math.sin(a0);        
    normals[off + 5] = 0;
    normals[off + 6] = Math.cos(a1);        
    normals[off + 7] = Math.sin(a1);        
    normals[off + 8] = 0;
    off += 9;
  }
}
 
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3));

const mesh = new THREE.Mesh( geometry, shaderMaterial );

scene.add(mesh);

const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector("canvas"),
});

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

function resize(renderer, camera, force) {
  const canvas = renderer.domElement;
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  if (force || canvas.width !== width || canvas.height !== height) {
    renderer.setSize(width, height, false);  // pass false so three doesn't mess up the css
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }
}

function render(time) {
  time *= 0.001;  // seconds
  
  resize(renderer, camera);
  
  uniforms.scale.value = Math.sin(time) * 5;
  
  renderer.render(scene, camera);

  requestAnimationFrame(render);
}
resize(renderer, camera, true);
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.min.js"></script>
<canvas></canvas>

Пример использования только центральной точки

const vs = `
uniform float scale;
void main() 
{
  vec3 pos = position + normal * scale;
  vec4 modelViewPosition = modelViewMatrix * vec4(pos, 1.0) ;
  gl_Position = projectionMatrix * modelViewPosition;
}
`;

const fs = `
void main() {
  gl_FragColor = vec4(1,0,0,1);
}
`;

const camera = new THREE.PerspectiveCamera(40, 1, 1, 1000);
camera.position.z = 400;

const scene = new THREE.Scene();

const uniforms = {
  scale: { value: 1 },
};

var shaderMaterial = new THREE.ShaderMaterial({
  uniforms:       uniforms,
  vertexShader:   vs,
  fragmentShader: fs,
});

const geometry = new THREE.BufferGeometry();
const numCircles = 20;
const numEdgePointsPerCircle = 36;
const numPointsPerCircle = numEdgePointsPerCircle * 3;
const totalPoints = numPointsPerCircle * numCircles;
const positions = new Float32Array(totalPoints * 3);
const normals = new Float32Array(totalPoints * 3);

const radius = 10;
let off = 0;

for (let c = 0; c < numCircles; ++c) {
  const cx = rand(-100, 100);
  const cy = rand(-100, 100);
  for (let p = 0; p < numEdgePointsPerCircle; ++p) {
    const a0 = (p + 0) * Math.PI * 2 / numEdgePointsPerCircle;
    const a1 = (p + 1) * Math.PI * 2 / numEdgePointsPerCircle;
    positions[off + 0] = cx;
    positions[off + 1] = cy;
    positions[off + 2] = 0;
    positions[off + 3] = cx;
    positions[off + 4] = cy;
    positions[off + 5] = 0;
    positions[off + 6] = cx;
    positions[off + 7] = cy;
    positions[off + 8] = 0;
    normals[off + 0] = 0;        
    normals[off + 1] = 0;        
    normals[off + 2] = 0;
    normals[off + 3] = Math.cos(a0);        
    normals[off + 4] = Math.sin(a0);        
    normals[off + 5] = 0;
    normals[off + 6] = Math.cos(a1);        
    normals[off + 7] = Math.sin(a1);        
    normals[off + 8] = 0;
    off += 9;
  }
}
 
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3));

const mesh = new THREE.Mesh( geometry, shaderMaterial );

scene.add(mesh);

const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector("canvas"),
});

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

function resize(renderer, camera, force) {
  const canvas = renderer.domElement;
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  if (force || canvas.width !== width || canvas.height !== height) {
    renderer.setSize(width, height, false);  // pass false so three doesn't mess up the css
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }
}

function render(time) {
  time *= 0.001;  // seconds
  
  resize(renderer, camera);
  
  uniforms.scale.value = 10 + Math.sin(time) * 5;
  
  renderer.render(scene, camera);

  requestAnimationFrame(render);
}
resize(renderer, camera, true);
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.min.js"></script>
<canvas></canvas>

Пример использования экземпляров

const vs = `
attribute vec3 center;
uniform float scale;
void main() 
{
  vec3 pos = center + position * scale;
  vec4 modelViewPosition = modelViewMatrix * vec4(pos, 1.0) ;
  gl_Position = projectionMatrix * modelViewPosition;
}
`;

const fs = `
void main() {
  gl_FragColor = vec4(1,0,0,1);
}
`;

const camera = new THREE.PerspectiveCamera(40, 1, 1, 1000);
camera.position.z = 400;

const scene = new THREE.Scene();

const uniforms = {
  scale: { value: 1 },
};

var shaderMaterial = new THREE.ShaderMaterial({
  uniforms:       uniforms,
  vertexShader:   vs,
  fragmentShader: fs,
});

// Just need one circle and 1 center point per circle

const geometry = new THREE.InstancedBufferGeometry();
const numCircles = 20;
const numEdgePointsPerCircle = 36;
const numPointsPerCircle = numEdgePointsPerCircle * 3;
const centers = new Float32Array(numCircles * 3);
const positions = new Float32Array(numPointsPerCircle * 3);

const radius = 10;
let off = 0;

for (let c = 0; c < numCircles; ++c) {
  const cx = rand(-100, 100);
  const cy = rand(-100, 100);
  centers[c * 2 + 0] = cx;
  centers[c * 2 + 1] = cy;
}

for (let p = 0; p < numEdgePointsPerCircle; ++p) {
  const a0 = (p + 0) * Math.PI * 2 / numEdgePointsPerCircle;
  const a1 = (p + 1) * Math.PI * 2 / numEdgePointsPerCircle;
  positions[off + 0] = 0;        
  positions[off + 1] = 0;        
  positions[off + 2] = 0;
  positions[off + 3] = Math.cos(a0);        
  positions[off + 4] = Math.sin(a0);        
  positions[off + 5] = 0;
  positions[off + 6] = Math.cos(a1);        
  positions[off + 7] = Math.sin(a1);        
  positions[off + 8] = 0;
  off += 9;
}
 
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('center', new THREE.InstancedBufferAttribute(centers, 3));

const mesh = new THREE.Mesh( geometry, shaderMaterial );

scene.add(mesh);

const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector("canvas"),
});

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

function resize(renderer, camera, force) {
  const canvas = renderer.domElement;
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  if (force || canvas.width !== width || canvas.height !== height) {
    renderer.setSize(width, height, false);  // pass false so three doesn't mess up the css
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }
}

function render(time) {
  time *= 0.001;  // seconds
  
  resize(renderer, camera);
  
  uniforms.scale.value = 10 + Math.sin(time) * 5;
  
  renderer.render(scene, camera);

  requestAnimationFrame(render);
}
resize(renderer, camera, true);
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.min.js"></script>
<canvas></canvas>

person gman    schedule 10.08.2017
comment
Это, кажется, не работает. Теперь они просто масштабируются почти незаметно. Просмотрел ваш отредактированный пост, предлагающий сохранить ссылку на центры кругов. Думаешь, мне стоит попробовать? - person N. Pyle; 10.08.2017
comment
Если ваши нормали плохие, просто добавление нормалей должно работать. Увеличьте масштаб. Какого размера ваши круги? Нормали обычно являются единичными векторами, поэтому масштаб 1 добавит 1 единицу. Если ваши круги размером в 1 единицу, они удвоятся в размере. Если ваши круги имеют размер 100 единиц, они вырастут до 101, только если масштаб равен 1. Использование другого решения точно такое же, как установка всех позиций для определенного круга в центральную точку, шейдер останется прежним. Если вы сохраните нормали, вы получите тот же результат. - person gman; 10.08.2017
comment
Просто чтобы сделать его более запутанным, вы можете использовать создание экземпляров, чтобы вам не приходилось повторять центральную точку каждой точки каждого круга. - person gman; 10.08.2017