使用片段着色器应用波浪效果时防止 WebGL 中的纹理边界拉伸
Preventing Texture Border Strectching in WebGL when Applying a Wave Effect with a Fragment Shader
我在我的项目中通过 Three.js 使用 GLSL。
我有一个像这样的旗帜纹理:
当我应用片段着色器为其赋予波浪效果时,纹理的边界似乎被拉伸了。但是,我不想要那样;我希望看到背景颜色(在本例中为清晰的),这样它看起来就像一面正在飘扬的旗帜。你可以在CodePen中看到问题here.
供参考(如果 CodePen 由于某种原因将来无法使用),代码如下:
顶点着色器
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
片段着色器
uniform vec2 u_resolution;
uniform float u_time;
uniform sampler2D image;
uniform vec2 strength;
uniform float speed;
varying vec2 vUv;
vec2 sineWave( vec2 p ) {
float x = strength.x * sin(-1.0 * p.y + 50.0 * p.x + u_time * speed);
float y = strength.y * sin(p.y + 10.0 * p.x + u_time * speed);
return vec2(p.x, p.y + y);
}
void main() {
vec4 color = texture2D(image, sineWave(vUv));
gl_FragColor = color;
}
Three.js代码
const CANVAS_WIDTH = 250;
const CANVAS_HEIGHT = 250;
const imageDataUri = 'data:image/png;base64,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';
const vertexShader = () => {
return `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`
};
const fragmentShader = () => {
return `
uniform vec2 u_resolution;
uniform float u_time;
uniform sampler2D image;
uniform vec2 strength;
uniform float speed;
varying vec2 vUv;
vec2 sineWave( vec2 p ) {
float x = strength.x * sin(-1.0 * p.y + 50.0 * p.x + u_time * speed);
float y = strength.y * sin(p.y + 10.0 * p.x + u_time * speed);
return vec2(p.x, p.y + y);
}
void main() {
vec4 color = texture2D(image, sineWave(vUv));
gl_FragColor = color;
}
`
};
function init() {
let loader = new THREE.TextureLoader();
loader.load(
imageDataUri,
(texture) => {
let scene = new THREE.Scene();
let camera =
new THREE.PerspectiveCamera(75, CANVAS_WIDTH / CANVAS_HEIGHT, 0.1, 1000);
let renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setClearColor(0x000000, 0);
renderer.setSize(CANVAS_WIDTH, CANVAS_HEIGHT);
document.body.appendChild(renderer.domElement);
let uniforms = {
u_time: { type: 'f', value: 0.1 },
u_resolution: { type: 'v2', value: new THREE.Vector2() },
image: { type: 't', value: texture },
strength: { type: 'v2', value: new THREE.Vector2(0.01, 0.025) },
speed: { type: 'f', value: 8.0 }
};
let material = new THREE.ShaderMaterial({
uniforms,
vertexShader: vertexShader(),
fragmentShader: fragmentShader()
});
let triangleMesh = new THREE.Mesh(new THREE.PlaneGeometry(CANVAS_WIDTH, CANVAS_HEIGHT, 1, 1), material);
scene.add(triangleMesh);
camera.position.z = 250;
let clock = new THREE.Clock();
let animate = () => {
requestAnimationFrame(animate);
triangleMesh.material.uniforms.u_time.value += clock.getDelta();
renderer.render(scene, camera);
};
animate();
},
undefined,
(err) => { console.error('Could not load texture', err) }
);
}
init();
body {background: orange;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
你可以做的是根据纹理的 v 坐标来缩放波浪效果。如果按 1.0-p.y
缩放,则波浪效应在顶部边界处为零,在底部边界处达到最大值。
float y = strength.y * sin(p.y + 10.0 * p.x + u_time * speed) * (1.0-p.y*p.y);
为了在旗帜的顶部区域产生较弱的波浪效果,按 v 坐标 ((1.0-p.y*p.y)
) 的平方缩放:
float y = strength.y * sin(p.y + 10.0 * p.x + u_time * speed) * (1.0-p.y*p.y);
const CANVAS_WIDTH = 250;
const CANVAS_HEIGHT = 250;
const imageDataUri = 'data:image/png;base64,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';
const vertexShader = () => {
return `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`
};
const fragmentShader = () => {
return `
uniform vec2 u_resolution;
uniform float u_time;
uniform sampler2D image;
uniform vec2 strength;
uniform float speed;
varying vec2 vUv;
vec2 sineWave( vec2 p ) {
float x = strength.x * sin(-1.0 * p.y + 50.0 * p.x + u_time * speed);
float y = strength.y * sin(p.y + 10.0 * p.x + u_time * speed) * (1.0-p.y*p.y);
return vec2(p.x, p.y + y);
}
void main() {
vec4 color = texture2D(image, sineWave(vUv));
gl_FragColor = color;
}
`
};
function init() {
let loader = new THREE.TextureLoader();
loader.load(
imageDataUri,
(texture) => {
let scene = new THREE.Scene();
let camera =
new THREE.PerspectiveCamera(75, CANVAS_WIDTH / CANVAS_HEIGHT, 0.1, 1000);
let renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setClearColor(0x000000, 0);
renderer.setSize(CANVAS_WIDTH, CANVAS_HEIGHT);
document.body.appendChild(renderer.domElement);
let uniforms = {
u_time: { type: 'f', value: 0.1 },
u_resolution: { type: 'v2', value: new THREE.Vector2() },
image: { type: 't', value: texture },
strength: { type: 'v2', value: new THREE.Vector2(0.01, 0.025) },
speed: { type: 'f', value: 8.0 }
};
let material = new THREE.ShaderMaterial({
uniforms,
vertexShader: vertexShader(),
fragmentShader: fragmentShader()
});
let triangleMesh = new THREE.Mesh(new THREE.PlaneGeometry(CANVAS_WIDTH, CANVAS_HEIGHT, 1, 1), material);
scene.add(triangleMesh);
camera.position.z = 250;
let clock = new THREE.Clock();
let animate = () => {
requestAnimationFrame(animate);
triangleMesh.material.uniforms.u_time.value += clock.getDelta();
renderer.render(scene, camera);
};
animate();
},
undefined,
(err) => { console.error('Could not load texture', err) }
);
}
init();
body {background: orange;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
我在我的项目中通过 Three.js 使用 GLSL。
我有一个像这样的旗帜纹理:
当我应用片段着色器为其赋予波浪效果时,纹理的边界似乎被拉伸了。但是,我不想要那样;我希望看到背景颜色(在本例中为清晰的),这样它看起来就像一面正在飘扬的旗帜。你可以在CodePen中看到问题here.
供参考(如果 CodePen 由于某种原因将来无法使用),代码如下:
顶点着色器
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
片段着色器
uniform vec2 u_resolution;
uniform float u_time;
uniform sampler2D image;
uniform vec2 strength;
uniform float speed;
varying vec2 vUv;
vec2 sineWave( vec2 p ) {
float x = strength.x * sin(-1.0 * p.y + 50.0 * p.x + u_time * speed);
float y = strength.y * sin(p.y + 10.0 * p.x + u_time * speed);
return vec2(p.x, p.y + y);
}
void main() {
vec4 color = texture2D(image, sineWave(vUv));
gl_FragColor = color;
}
Three.js代码
const CANVAS_WIDTH = 250;
const CANVAS_HEIGHT = 250;
const imageDataUri = 'data:image/png;base64,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';
const vertexShader = () => {
return `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`
};
const fragmentShader = () => {
return `
uniform vec2 u_resolution;
uniform float u_time;
uniform sampler2D image;
uniform vec2 strength;
uniform float speed;
varying vec2 vUv;
vec2 sineWave( vec2 p ) {
float x = strength.x * sin(-1.0 * p.y + 50.0 * p.x + u_time * speed);
float y = strength.y * sin(p.y + 10.0 * p.x + u_time * speed);
return vec2(p.x, p.y + y);
}
void main() {
vec4 color = texture2D(image, sineWave(vUv));
gl_FragColor = color;
}
`
};
function init() {
let loader = new THREE.TextureLoader();
loader.load(
imageDataUri,
(texture) => {
let scene = new THREE.Scene();
let camera =
new THREE.PerspectiveCamera(75, CANVAS_WIDTH / CANVAS_HEIGHT, 0.1, 1000);
let renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setClearColor(0x000000, 0);
renderer.setSize(CANVAS_WIDTH, CANVAS_HEIGHT);
document.body.appendChild(renderer.domElement);
let uniforms = {
u_time: { type: 'f', value: 0.1 },
u_resolution: { type: 'v2', value: new THREE.Vector2() },
image: { type: 't', value: texture },
strength: { type: 'v2', value: new THREE.Vector2(0.01, 0.025) },
speed: { type: 'f', value: 8.0 }
};
let material = new THREE.ShaderMaterial({
uniforms,
vertexShader: vertexShader(),
fragmentShader: fragmentShader()
});
let triangleMesh = new THREE.Mesh(new THREE.PlaneGeometry(CANVAS_WIDTH, CANVAS_HEIGHT, 1, 1), material);
scene.add(triangleMesh);
camera.position.z = 250;
let clock = new THREE.Clock();
let animate = () => {
requestAnimationFrame(animate);
triangleMesh.material.uniforms.u_time.value += clock.getDelta();
renderer.render(scene, camera);
};
animate();
},
undefined,
(err) => { console.error('Could not load texture', err) }
);
}
init();
body {background: orange;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
你可以做的是根据纹理的 v 坐标来缩放波浪效果。如果按 1.0-p.y
缩放,则波浪效应在顶部边界处为零,在底部边界处达到最大值。
float y = strength.y * sin(p.y + 10.0 * p.x + u_time * speed) * (1.0-p.y*p.y);
为了在旗帜的顶部区域产生较弱的波浪效果,按 v 坐标 ((1.0-p.y*p.y)
) 的平方缩放:
float y = strength.y * sin(p.y + 10.0 * p.x + u_time * speed) * (1.0-p.y*p.y);
const CANVAS_WIDTH = 250;
const CANVAS_HEIGHT = 250;
const imageDataUri = 'data:image/png;base64,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';
const vertexShader = () => {
return `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`
};
const fragmentShader = () => {
return `
uniform vec2 u_resolution;
uniform float u_time;
uniform sampler2D image;
uniform vec2 strength;
uniform float speed;
varying vec2 vUv;
vec2 sineWave( vec2 p ) {
float x = strength.x * sin(-1.0 * p.y + 50.0 * p.x + u_time * speed);
float y = strength.y * sin(p.y + 10.0 * p.x + u_time * speed) * (1.0-p.y*p.y);
return vec2(p.x, p.y + y);
}
void main() {
vec4 color = texture2D(image, sineWave(vUv));
gl_FragColor = color;
}
`
};
function init() {
let loader = new THREE.TextureLoader();
loader.load(
imageDataUri,
(texture) => {
let scene = new THREE.Scene();
let camera =
new THREE.PerspectiveCamera(75, CANVAS_WIDTH / CANVAS_HEIGHT, 0.1, 1000);
let renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setClearColor(0x000000, 0);
renderer.setSize(CANVAS_WIDTH, CANVAS_HEIGHT);
document.body.appendChild(renderer.domElement);
let uniforms = {
u_time: { type: 'f', value: 0.1 },
u_resolution: { type: 'v2', value: new THREE.Vector2() },
image: { type: 't', value: texture },
strength: { type: 'v2', value: new THREE.Vector2(0.01, 0.025) },
speed: { type: 'f', value: 8.0 }
};
let material = new THREE.ShaderMaterial({
uniforms,
vertexShader: vertexShader(),
fragmentShader: fragmentShader()
});
let triangleMesh = new THREE.Mesh(new THREE.PlaneGeometry(CANVAS_WIDTH, CANVAS_HEIGHT, 1, 1), material);
scene.add(triangleMesh);
camera.position.z = 250;
let clock = new THREE.Clock();
let animate = () => {
requestAnimationFrame(animate);
triangleMesh.material.uniforms.u_time.value += clock.getDelta();
renderer.render(scene, camera);
};
animate();
},
undefined,
(err) => { console.error('Could not load texture', err) }
);
}
init();
body {background: orange;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>