Three.js 三点闪烁。基于相机位置和纹理坐标,但仅在Nvidia卡上

Three.js 三点闪烁。基于相机位置和纹理坐标,但仅在Nvidia卡上,three.js,Three.js,我有一个闪烁三点的问题。点取决于它们的UV坐标,如以下代码笔所示: 代码笔中的代码尽可能精简(171行), 但总结一下我正在做的事情: 使用三个点渲染精灵 BufferGeometry包含每个精灵的精灵表索引和位置 RawShaderMaterial具有自定义顶点和像素着色器,用于查找给定索引的精灵的UV坐标 带有4x4单元格的128x128px精灵表包含精灵 代码如下: /// FRAGMENT SHADER ========================================

我有一个闪烁三点的问题。点取决于它们的UV坐标,如以下代码笔所示:

代码笔中的代码尽可能精简(171行), 但总结一下我正在做的事情:

  • 使用三个点渲染精灵
  • BufferGeometry包含每个精灵的精灵表索引和位置
  • RawShaderMaterial具有自定义顶点和像素着色器,用于查找给定索引的精灵的UV坐标
  • 带有4x4单元格的128x128px精灵表包含精灵
代码如下:

/// FRAGMENT SHADER ===========================================================
const fragmentShader = `
precision highp float;

uniform sampler2D spritesheet;

// number of spritesheet subdivisions both vertically and horizontally
// e.g. for a 4x4 spritesheet this number is 4
uniform float spritesheetSubdivisions;

// vParams[i].x = sprite index
// vParams[i].z = sprite alpha
varying vec3 vParams;

/**
 * Maps regular UV coordinates spanning the entire spritesheet
 * to a specific sprite within the spritesheet based on the given index,
 * which points into a spritesheel cell (depending on spritesheetSubdivisions
 * and assuming that the spritesheet is regular and square).
 */
vec2 spriteIndexToUV(float idx, vec2 uv) {
    float cols = spritesheetSubdivisions;
    float rows = spritesheetSubdivisions;

    float x = mod(idx, cols);
    float y = floor(idx / cols);

    return vec2(x / cols + uv.x / cols, 1.0 - (y / rows + (uv.y) / rows));
}

void main() {
    vec2 uv = spriteIndexToUV(vParams.x, gl_PointCoord);
    vec4 diffuse = texture2D(spritesheet, uv);

    float alpha = diffuse.a * vParams.z;
    if (alpha < 0.5) discard;

    gl_FragColor = vec4(diffuse.xyz, alpha);
}
`

// VERTEX SHADER ==============================================================
const vertexShader = `
precision highp float;

uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform float size;
uniform float scale;

attribute vec3 position;
attribute vec3 params; // x = sprite index, y = unused, z = sprite alpha
attribute vec3 color;

varying vec3 vParams;

void main() {
    vParams = params;

    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
    gl_Position = projectionMatrix * mvPosition;
    gl_PointSize = size * ( scale / - mvPosition.z );
}
`

// THREEJS CODE ===============================================================

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

const renderer = new THREE.WebGLRenderer({canvas: document.querySelector("#mycanvas")});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xf0f0f0)

const pointGeometry = new THREE.BufferGeometry()
pointGeometry.addAttribute("position", new THREE.BufferAttribute(new Float32Array([
  -1.5, -1.5, 0,
  -0.5, -1.5, 0,
  0.5, -1.5, 0,
  1.5, -1.5, 0,

  -1.5, -0.5, 0,
  -0.5, -0.5, 0,
  0.5, -0.5, 0,
  1.5, -0.5, 0,

  -1.5, 0.5, 0,
  -0.5, 0.5, 0,
  0.5, 0.5, 0,
  1.5, 0.5, 0,

  -1.5, 1.5, 0,
  -0.5, 1.5, 0,
  0.5, 1.5, 0,
  1.5, 1.5, 0,
]), 3))

pointGeometry.addAttribute("params", new THREE.BufferAttribute(new Float32Array([
  0, 0, 1,    // sprite index 0 (row 0, column 0)
  1, 0, 1,    // sprite index 1 (row 0, column 1)
  2, 0, 1,    // sprite index 2 (row 0, column 2)
  3, 0, 1,    // sprite index 3 (row 0, column 4)

  4, 0, 1,    // sprite index 4 (row 1, column 0)
  5, 0, 1,    // sprite index 5 (row 1, column 1)
  6, 0, 1,    // ...
  7, 0, 1,

  8, 0, 1,
  9, 0, 1,
  10, 0, 1,
  11, 0, 1,

  12, 0, 1,
  13, 0, 1,
  14, 0, 1,
  15, 0, 1
]), 3))

const img = document.querySelector("img")
const texture = new THREE.TextureLoader().load(img.src);

const pointMaterial = new THREE.RawShaderMaterial({
  transparent: true,
  vertexShader: vertexShader,
  fragmentShader: fragmentShader,
  uniforms: {
    spritesheet: {
      type: "t",
      value: texture
    },
    spritesheetSubdivisions: {
      type: "f",
      value: 4
    },
    size: {
      type: "f",
      value: 1
    },
    scale: {
      type: "f",
      value: window.innerHeight / 2
    }
  }
})

const points = new THREE.Points(pointGeometry, pointMaterial)
scene.add(points)

const render = function (timestamp) {
  requestAnimationFrame(render);


  camera.position.z = 5 + Math.sin(timestamp / 1000.0)

  renderer.render(scene, camera);
};

render();

// resize viewport
window.addEventListener( 'resize', onWindowResize, false );

function onWindowResize(){

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize( window.innerWidth, window.innerHeight );

}
///片段着色器===========================================================
常量碎片着色器=`
高精度浮点;
均匀采样2D spritesheet;
//垂直和水平精灵表细分的数量
//例如,对于4x4 spritesheet,该数字为4
均匀浮动精灵表分区;
//vParams[i].x=精灵索引
//vParams[i].z=精灵alpha
可变vec3参数;
/**
*映射跨越整个精灵表的常规UV坐标
*根据给定的索引,在精灵表中创建特定精灵,
*指向spritesheel单元格(取决于spritesheetSubdivisions
*假设精灵表是规则的和正方形的)。
*/
vec2 spriteIndexToUV(浮动idx,vec2 uv){
float cols=spritesheetSubdivisions;
浮动行=spritesheetSubdivisions;
浮点数x=mod(idx,cols);
浮动y=地板(idx/cols);
返回vec2(x/cols+uv.x/cols,1.0-(y/rows+(uv.y)/rows));
}
void main(){
vec2 uv=spriteIndexToUV(vParams.x,gl_PointCoord);
vec4漫反射=纹理2D(精灵片,uv);
float alpha=漫反射.a*vParams.z;
如果(α<0.5)丢弃;
gl_FragColor=vec4(漫反射.xyz,α);
}
`
//顶点着色器==============================================================
常量顶点着色器=`
高精度浮点;
统一mat4模型视图矩阵;
均匀mat4投影矩阵;
均匀浮动尺寸;
均匀浮标;
属性向量3位置;
属性vec3参数;//x=精灵索引,y=未使用,z=精灵alpha
属性向量3颜色;
可变vec3参数;
void main(){
vParams=参数;
vec4 mvPosition=modelViewMatrix*vec4(位置,1.0);
gl_位置=投影矩阵*mvPosition;
gl_PointSize=尺寸*(比例/-mvPosition.z);
}
`
//THREEJS代码===============================================================
const scene=new THREE.scene();
const-camera=新的三视角摄像机(75,window.innerWidth/window.innerHeight,0.11000);
const renderer=new THREE.WebGLRenderer({canvas:document.querySelector(“#mycanvas”)});
renderer.setSize(window.innerWidth、window.innerHeight);
renderer.setClearColor(0xF0)
const pointGeometry=new THREE.BufferGeometry()
pointGeometry.addAttribute(“位置”,新的三个.BufferAttribute(新的Float32Array([
-1.5, -1.5, 0,
-0.5, -1.5, 0,
0.5, -1.5, 0,
1.5, -1.5, 0,
-1.5, -0.5, 0,
-0.5, -0.5, 0,
0.5, -0.5, 0,
1.5, -0.5, 0,
-1.5, 0.5, 0,
-0.5, 0.5, 0,
0.5, 0.5, 0,
1.5, 0.5, 0,
-1.5, 1.5, 0,
-0.5, 1.5, 0,
0.5, 1.5, 0,
1.5, 1.5, 0,
]), 3))
pointGeometry.addAttribute(“params”,新的THREE.BufferAttribute(新的Float32Array([
0,0,1,//精灵索引0(第0行,第0列)
1,0,1,//精灵索引1(第0行第1列)
2,0,1,//精灵索引2(第0行第2列)
3,0,1,//精灵索引3(第0行第4列)
4,0,1,//精灵索引4(第1行,第0列)
5,0,1,//精灵索引5(第1行第1列)
6, 0, 1,    // ...
7, 0, 1,
8, 0, 1,
9, 0, 1,
10, 0, 1,
11, 0, 1,
12, 0, 1,
13, 0, 1,
14, 0, 1,
15, 0, 1
]), 3))
常量img=document.querySelector(“img”)
const texture=new THREE.TextureLoader().load(img.src);
const pointMaterial=新的三点材质。RawShaderMaterial({
透明:是的,
vertexShader:vertexShader,
fragmentShader:fragmentShader,
制服:{
精神片:{
类型:“t”,
值:纹理
},
spritesheetSubdivisions:{
类型:“f”,
价值:4
},
尺寸:{
类型:“f”,
价值:1
},
比例:{
类型:“f”,
值:window.innerHeight/2
}
}
})
常量点=新的三个点(点几何体、点材质)
场景。添加(点)
const render=函数(时间戳){
请求动画帧(渲染);
camera.position.z=5+Math.sin(时间戳/1000.0)
渲染器。渲染(场景、摄影机);
};
render();
//调整视口大小
addEventListener('resize',onWindowResize,false);
函数onWindowResize(){
camera.aspect=window.innerWidth/window.innerHeight;
camera.updateProjectMatrix();
renderer.setSize(window.innerWidth、window.innerHeight);
}
如果你有一个Nvidia卡,你会看到三个精灵闪烁,而相机 正在沿Z轴来回移动。集成英特尔图形芯片 问题没有出现

我不知道如何解决这个问题。受影响的uv坐标似乎是随机的。如果有任何提示,我将不胜感激。

spriteIndexToUV()函数中的mod()/floor()计算会在某些星座中造成问题(当spriteindex是spritesheetSubdivisions的倍数时)

我可以通过用一个小ε调整cols变量来修复它:

vec2 spriteIndexToUV(float idx, vec2 uv) 
{
  float cols = spritesheetSubdivisions - 1e-6; // subtract epsilon
  float rows = spritesheetSubdivisions;

  float x = mod(idx, cols);
  float y = floor(idx / cols);

  return vec2(x / cols + uv.x / cols, 1.0 - (y / rows + (uv.y) / rows));
}
PS:代码笔的东西真的很酷,我不知道它的存在:-)

编辑:这样写可能更好/更清晰:

float cols = spritesheetSubdivisions;
float rows = spritesheetSubdivisions;

float y = floor ((idx+0.5) / cols);
float x = idx - cols * y;
这样,我们就可以完全避开地板操作中的任何关键情况——另外,我们还可以去掉mod()调用

至于为什么
floor(idx/4)
idx
应该正好为4.0时有时会产生0而不是1,我只能推测
变化的vec3 vParams
在从顶点着色器到片段着色器阶段时会受到一些插值,从而导致片段着色器