Обрезать текстуру с правильными UV внутри деформированного прямоугольника в WebGL / Threejs - PullRequest
0 голосов
/ 01 апреля 2020

Эй, я пытаюсь сделать обрезанную текстуру внутри эффекта рамки, как это. Идея состоит в том, что рамка может быть свободно обернута и преобразована вокруг, в то время как текстура остается правильно сэмплированной, отсюда и эффект «портала» или «рамки», о котором я говорю:

image of desired wrapped effect

Вот мой фрагмент кода с соответствующим JS и кодом шейдера:

const photoUrl = 'https://images.unsplash.com/photo-1583287916880-530d57049c66?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ'

const appWidth = innerWidth
const appHeight = innerHeight

const renderer = new THREE.WebGLRenderer()
const camera = new THREE.OrthographicCamera(appWidth / - 2, appWidth / 2, appHeight / 2, appHeight / - 2, 1, 1000)
const scene = new THREE.Scene()

camera.position.set(0, 0, 50)
camera.lookAt(new THREE.Vector3(0, 0, 0))

renderer.setClearColor(0xAAAAAA)
renderer.setPixelRatio(window.devicePixelRatio || 1)
renderer.setSize(appWidth, appHeight)
document.body.appendChild(renderer.domElement)

const vertexShader = `
varying vec2 v_uv;
void main () {

  vec3 newPosition = position;
  float dist = distance(position, vec3(0.0));
  newPosition.x += sin(3.0) * dist * 3.0;
  // newPosition.y += cos(0.1) * dist * 0.2;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
  v_uv = uv;

  // I guess do some magic with v_uv to inverse transform it based on the vertices transformation??

}
`
const fragmentShader = `
uniform sampler2D u_sampler;
uniform vec2 u_imageSize;
varying vec2 v_uv;

void main () {
  vec2 s = vec2(250.0, 400.0);
  vec2 i = u_imageSize;
  float rs = s.x / s.y;
  float ri = i.x / i.y;
  vec2 new = rs < ri ? vec2(i.x * s.y / i.y, s.y) : vec2(s.x, i.y * s.x / i.x);
  vec2 offset = (rs < ri ? vec2((new.x - s.x) / 2.0, 0.0) : vec2(0.0, (new.y - s.y) / 2.0)) / new;
  vec2 uv = v_uv * s / new + offset;
  // sample texture like css background-size: cover
  // OR if the texture is the same size as plane, apply some matrix transform to UVs so there is extra padding and you dont see the texture corners when the plane is deformed at maximum?
  gl_FragColor = texture2D(u_sampler, uv);
}
`

const mesh = new THREE.Mesh(
  new THREE.PlaneGeometry(250, 400, 20, 20),
  new THREE.ShaderMaterial({
    uniforms: {
      u_sampler: { value: null },
      u_imageSize: { value: new THREE.Vector2(0, 0) },
    },
    vertexShader,
    fragmentShader,
  })
)
scene.add(mesh)

new THREE.TextureLoader().load(photoUrl, texture => {
  mesh.material.uniforms.u_sampler.value = texture
  mesh.material.uniforms.u_imageSize.value.x = texture.image.naturalWidth
  mesh.material.uniforms.u_imageSize.value.y = texture.image.naturalHeight
  mesh.material.needsUpdate = true
})

updateFrame()
function updateFrame () {
  renderer.render(scene, camera)
  requestAnimationFrame(updateFrame)
}
* { margin: 0; padding: 0; } 
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>

Как и ожидалось, получится плоскость с обоими положениями вершин и UV-оболочкой. Я изо всех сил пытаюсь обернуть мою голову вокруг математики и порядка операций позади этого. Заранее спасибо!

PS Кроме того, если я использую текстуру, точно такую ​​же, как моя плоскость с точки зрения размера пикселя (я использую камеру orthographi c), я думаю, мне нужно увеличить ее, чтобы расширить за пределы границы самолета, поэтому, когда самолет трансформируется, вы не видите его края / кровотечение? Как можно go об этом?

1 Ответ

1 голос
/ 02 апреля 2020

Исходная формула для vUv:

v_uv.x = position.x/width+0.5;

Поскольку вы изменили положение. Теперь становится,

v_uv.x = newPosition.x/width+0.5;

const photoUrl = 'https://images.unsplash.com/photo-1583287916880-530d57049c66?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ'

const appWidth = innerWidth
const appHeight = innerHeight

const renderer = new THREE.WebGLRenderer()
const camera = new THREE.OrthographicCamera(appWidth / - 2, appWidth / 2, appHeight / 2, appHeight / - 2, 1, 1000)
const scene = new THREE.Scene()

camera.position.set(0, 0, 50)
camera.lookAt(new THREE.Vector3(0, 0, 0))

renderer.setClearColor(0xAAAAAA)
renderer.setPixelRatio(window.devicePixelRatio || 1)
renderer.setSize(appWidth, appHeight)
document.body.appendChild(renderer.domElement)

const vertexShader = `
varying vec2 v_uv;
uniform vec2 u_imageSize;
void main () {

  vec3 newPosition = position;
  float dist = distance(position, vec3(0.0));
  newPosition.x += sin(3.0) * dist * 3.0;
  newPosition.y += cos(0.1) * dist * 0.2;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
  v_uv = uv;
  //v_uv.x = position.x/250.0+0.5;
  //v_uv.y = position.y/400.0+0.5;
  v_uv.x = newPosition.x/250.0+0.5;
  v_uv.y = newPosition.y/400.0+0.5;
}
`
const fragmentShader = `
uniform sampler2D u_sampler;
uniform vec2 u_imageSize;
varying vec2 v_uv;

void main () {
  vec2 s = vec2(250.0, 400.0);
  vec2 i = u_imageSize;
  float rs = s.x / s.y;
  float ri = i.x / i.y;
  vec2 new = rs < ri ? vec2(i.x * s.y / i.y, s.y) : vec2(s.x, i.y * s.x / i.x);
  vec2 offset = (rs < ri ? vec2((new.x - s.x) / 2.0, 0.0) : vec2(0.0, (new.y - s.y) / 2.0)) / new;
  vec2 uv = v_uv * s / new + offset;
  // sample texture like css background-size: cover
  // OR if the texture is the same size as plane, apply some matrix transform to UVs so there is extra padding and you dont see the texture corners when the plane is deformed at maximum?
  gl_FragColor = texture2D(u_sampler, uv);
}
`

const mesh = new THREE.Mesh(
  new THREE.PlaneGeometry(250, 400, 20, 20),
  new THREE.ShaderMaterial({
    uniforms: {
      u_sampler: { value: null },
      u_imageSize: { value: new THREE.Vector2(0, 0) },
    },
    vertexShader,
    fragmentShader,
  })
)
scene.add(mesh)

new THREE.TextureLoader().load(photoUrl, texture => {
  mesh.material.uniforms.u_sampler.value = texture
  mesh.material.uniforms.u_imageSize.value.x = texture.image.naturalWidth
  mesh.material.uniforms.u_imageSize.value.y = texture.image.naturalHeight
  mesh.material.needsUpdate = true
})

updateFrame()
function updateFrame () {
  renderer.render(scene, camera)
  requestAnimationFrame(updateFrame)
}
* { margin: 0; padding: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
...