Position FBO
canvas画布纹理
使用canvas API书写文字后,将canvas作为纹理。使用uv坐标获取canvas纹理上一点,如果大于0,说明是文字,否则是背景。
随机坐标
随机生成长宽和canvas纹理一样的随机坐标纹理。
Vertex shader
由于该fbo只是用来计算位置,然后将位置作为纹理输出到renderTarget里给最终renderer使用的,所以vertex shader很简单,直接平面的position做mvp变换即可,最终还是平面,重要的是每个平面的颜色记载了这个uv对应的点的位置。
const positionSimulationVertexShader = `
// this value stores the texture coordinates the data for this vertex is stored in
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`
Fragement shader
fbo类中会自动生成一个plane几何体,上面的canvas纹理和随机坐标纹理都作为uniform变量传入到自定义的shader中。
在vertex shader中使用uv坐标分别从canvas纹理获取透明度,从默认坐标纹理获取随机坐标,表示的是当前屏幕这一点被打乱后的所在位置,如果说canvas透明度大于0,也就是文字,那么需要将打乱的点复位。也就是需要将随机坐标移动到当前uv。
目标是将default position 移动到uv附近
const positionSimulationFragmentShader = `
/** generates a random number between 0 and 1 **/
highp float rand(vec2 co) {
highp float a = 12.9898;
highp float b = 78.233;
highp float c = 43758.5453;
highp float dt= dot(co.xy ,vec2(a,b));
highp float sn= mod(dt,3.14);
return fract(sin(sn) * c);
}
// this is the texture position the data for this particle is stored in
varying vec2 vUv;
uniform sampler2D tPrev;
uniform sampler2D tCurr;
uniform sampler2D tDefaultPosition;
uniform sampler2D tText;
uniform float topSpeed;
uniform float acceleration;
uniform float textPositionMultiplier;
vec3 setTopSpeed(vec3 speed, float topSpeed) {
return vec3(
speed.x > topSpeed ? topSpeed : speed.x < -topSpeed ? -topSpeed : speed.x,
speed.y > topSpeed ? topSpeed : speed.y < -topSpeed ? -topSpeed : speed.y,
speed.z > topSpeed ? topSpeed : speed.z < -topSpeed ? -topSpeed : speed.z
);
}
vec3 moveParticleToGoal(vec3 currPos, vec3 prevPos, vec3 goal) {
vec3 distanceToGoal = goal - currPos;
vec3 currVelocity = currPos - prevPos;
vec3 calculatedAcceleration = normalize(distanceToGoal) * acceleration;
float currVelocityL = length(currVelocity);
float distanceToGoalL = length(distanceToGoal);
if (distanceToGoalL > currVelocityL) {
vec3 velocity = currVelocity + calculatedAcceleration;
velocity = setTopSpeed(velocity, topSpeed);
return currPos + velocity;
} else {
return goal;
}
}
void main() {
vec3 defaultPos = texture2D(tDefaultPosition, vUv).xyz;
vec3 prevPos = texture2D(tPrev, vUv).xyz;
vec3 position = texture2D(tCurr, vUv).xyz;
float textOpacity = texture2D(tText, vUv).r;
float isTextParticle = 0.0;
if (prevPos == vec3(0.0, 0.0, 0.0)) {
position = defaultPos;
}
if (textOpacity > 0.0) {
position = moveParticleToGoal(position, prevPos, vec3((vUv - 0.5) * textPositionMultiplier, 0.0));
isTextParticle = 1.0;
}
else {
position = moveParticleToGoal(position, prevPos, defaultPos);
}
// write new positions out
gl_FragColor = vec4(position, isTextParticle);
}
`
Renderer
几何体数据
生成一个长宽和canvas纹理一样的顶点数组,每个点是uv坐标。这样在renderer自定义shader里的position获取到的就是uv坐标。(效果和生成plane几何体类似)。但是这里使用Points渲染方式。
Vertex shader
这里输入的数据是一个平面,渲染方式是点,显然我不是想渲染一个点平面,如果vertex shader是最简单的mvp转换,那最终就是点平面。所以vertex shader需要根据输入position去计算出真正点的位置。上面说过position实际是uv,那么根据uv到positionFbo对应的uniform里去取出当前uv对应的真实坐标,对该坐标mvp转换就拿到了gl_position。
gl_PointSize根据移动的比率计算最小值和最大值的插值。
const vertexShader = `
uniform sampler2D tPosition;
uniform float sizeMultiplierForScreen;
uniform float textSizeMultiplier;
uniform sampler2D tDefaultPosition;
uniform sampler2D tSize;
varying vec2 vUv;
void main() {
// gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
// gl_PointSize = 9.0;
vUv = position.xy;
vec3 position = texture2D(tPosition, vUv).xyz;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * mvPosition;
vec3 goal = vec3((vUv - 0.5) * 4.0, 0);
vec3 defaultPosition = texture2D(tDefaultPosition, vUv).xyz;
float distanceToTravel = length(goal - defaultPosition);
float distanceTravelled = length(position - defaultPosition);
float distanceTravelledRatio = distanceTravelled / distanceToTravel;
// float size = 0.3;
float size = texture2D(tSize, vUv).a ;
float textSize = size * size * textSizeMultiplier; // multiply star size against itself to create a size range when a text-star
size = distanceTravelled > 0.0 ? mix(size, textSize, distanceTravelledRatio > 1.0 ? 1.0 : distanceTravelledRatio) : size;
gl_PointSize = size * (sizeMultiplierForScreen / -mvPosition.z);
}
`
Fragment shader
根据uv可以获取到当前位置的透明度,也就是是否为文字;可以从颜色纹理中得到一个颜色值;
根据gl_PointCoord从一张图片里获取到一个颜色值,最终颜色相乘。
gl_PointCoord的效果就是通过从纹理中采样设置颜色的方式,将points渲染模式默认的方形,转换成一张图片纹理
const fragmentShader = `
uniform sampler2D tPosition;
uniform sampler2D starImg;
uniform sampler2D tColour;
varying vec2 vUv;
void main() {
vec4 colour = texture2D(tColour, vUv).rgba;
float isTextColor = texture2D(tPosition, vUv).a;
if (isTextColor > 0.95) {
gl_FragColor = colour * texture2D(starImg, gl_PointCoord);
}
}
`