我是 ThreeJS 的新手,我有一个 Shadertoy 着色器(这个),我想将其嵌入到我的网页中。它利用缓冲区。我无法找到有关该主题的很多有用信息。主图像着色器应用热图颜色方案,而缓冲区着色器使用一些数学来求解 2D 热方程,这就是 Shadertoy 着色器的作用。缓冲区着色器在这些计算中使用其先前的状态。作为参考,我按照 this Stackoverflow 帖子中的说明进行操作。
这是主图:
out vec4 col;
uniform sampler2D tex;
void main()
{
float t = texelFetch(tex, ivec2(gl_FragCoord.xy), 0).x;
col = vec4(
sqrt(t),
t * t * t,
max(sin(6.283 * t), 0.),
t
);
}
这是缓冲区:
#define R 8.
#define DT 0.1
out vec4 col;
uniform sampler2D tex;
uniform float alpha;
uniform vec2 iMouse;
vec4 laplace(vec2 p)
{
// 5-point stencil Laplacian
vec4 c = texelFetch(tex, ivec2(p), 0);
vec4 lt = texelFetch(tex, ivec2(p + vec2(-1, 0)), 0);
vec4 rt = texelFetch(tex, ivec2(p + vec2(1, 0)), 0);
vec4 up = texelFetch(tex, ivec2(p + vec2(0, -1)), 0);
vec4 dn = texelFetch(tex, ivec2(p + vec2(0, 1)), 0);
return lt + up + rt + dn - (4. * c);
}
void main()
{
vec2 p = gl_FragCoord.xy;
if (distance(p, iMouse) < R)
{
col = vec4(1.0);
} else {
// Euler integration
vec4 T = texelFetch(tex, ivec2(p), 0);
vec4 iT = alpha * laplace(p);
col = T + iT * DT;
}
}
这是我的JS代码:
let canvas, renderer, camera, renderTarg;
let size;
let count = 2;
let scene0, plane0, fragment0, uniforms0;
let scene1, plane1, fragment1, uniforms1;
let drawing = false;
let a = 1;
let coord = [200, 200];
document.addEventListener("DOMContentLoaded", function() {
// Set up canvas and renderers
canvas = document.getElementById("canv");
canvas.addEventListener("mousedown", startDraw);
canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mouseup", endDraw);
canvas.addEventListener("mouseout", endDraw);
size = window.innerHeight;
renderer = new THREE.WebGLRenderer({
canvas,
preserveDrawingBuffer: true
});
// Initialize objects and cameras
scene0 = new THREE.Scene();
scene1 = new THREE.Scene();
plane0 = new THREE.PlaneGeometry(2, 2);
plane1 = new THREE.PlaneGeometry(2, 2);
renderTarg = new THREE.WebGLRenderTarget(size, size);
// Load shaders
let loader = new THREE.FileLoader();
function next() {
count--;
if (count == 0)
load();
}
loader.load("static/buffer.frag", (dat) => {fragment0 = dat; next();});
loader.load("static/mainShader.frag", (dat) => {fragment1 = dat; next();});
});
function load() {
// Create meshes and apply shaders
uniforms0 = {tex: {value: new THREE.Texture()}, iMouse: {value: new THREE.Vector2()}, alpha: {value: a}};
uniforms1 = {tex: {value: new THREE.Texture()}};
let mat0 = new THREE.ShaderMaterial({fragmentShader: fragment0, uniforms: uniforms0, glslVersion: THREE.GLSL3});
let mat1 = new THREE.ShaderMaterial({fragmentShader: fragment1, uniforms: uniforms1, glslVersion: THREE.GLSL3});
let planeMesh0 = new THREE.Mesh(plane0, mat0);
let planeMesh1 = new THREE.Mesh(plane1, mat1);
scene0.add(planeMesh0);
scene1.add(planeMesh1);
camera = new THREE.OrthographicCamera(-1, 1, 1, -1, -1, 1);
renderer.setSize(size, size, false);
animLoop();
}
function animLoop() {
// Render buffer
uniforms0.tex.value = renderTarg.texture;
uniforms0.iMouse.value.set(coord[0], coord[1]);
uniforms0.alpha.value = a;
renderer.render(scene0, camera, renderTarg);
// Render main
uniforms1.tex.value = renderTarg.texture;
renderer.render(scene1, camera);
window.requestAnimationFrame(animLoop);
}
/*
Event handlers for clicking and dragging, to trace path.
*/
const startDraw = (e) => {
drawing = true;
draw(e);
}
const draw = (e) => {
// Draw on canvas
if (drawing) {
let x = e.pageX - canvas.offsetLeft;
let y = size - (e.pageY - canvas.offsetTop);
coord = [x, y];
}
}
const endDraw = (e) => {
drawing = false;
}
当我运行它时,我只是看到黑屏,没有错误消息。当我注释掉对 renderer.render 的第二次调用时,我得到一个跟随光标的白色圆圈。
提前致谢。
可以尝试下面的代码,用 Threejs.K 来实现缓冲效果
<script>
const VERTEX_SHADER = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`;
const BUFFER_A_FRAG = `
uniform vec4 iMouse;
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform vec2 iResolution;
uniform float iFrame;
varying vec2 vUv;
#define mousedata(a,b) texture2D( iChannel1, (0.5+vec2(a,b)) / iResolution.xy, -0.0 )
#define backbuffer(uv) texture2D( iChannel0, uv ).xy
float lineDist(vec2 p, vec2 start, vec2 end, float width) {
vec2 dir = start - end;
float lngth = length(dir);
dir /= lngth;
vec2 proj = max(0.0, min(lngth, dot((start - p), dir))) * dir;
return length( (start - p) - proj ) - (width / 2.0);
}
void main() {
vec2 uv = vUv;
vec2 col = uv;
if (iFrame > 2.) {
col = texture2D(iChannel0,uv).xy;
vec2 mouse = iMouse.xy/iResolution.xy;
vec2 p_mouse = mousedata(2.,0.).xy;
if (mousedata(4.,0.).x > 0.) {
col = backbuffer(uv+((p_mouse-mouse)*clamp(1.-(lineDist(uv,mouse,p_mouse,0.)*20.),0.,1.)*.7));
}
}
gl_FragColor = vec4(col,0.0,1.0);
}
`;
const BUFFER_B_FRAG = `
uniform vec4 iMouse;
uniform sampler2D iChannel0;
uniform vec3 iResolution;
varying vec2 vUv;
bool pixelAt(vec2 coord, float a, float b) {
return (floor(coord.x) == a && floor(coord.y) == b);
}
vec4 backbuffer(float a,float b) {
return texture2D( iChannel0, (0.5+vec2(a,b)) / iResolution.xy, -100.0 );
}
void main( ) {
vec2 uv = vUv;// / iResolution.xy;
vec4 color = texture2D(iChannel0,uv);
if (pixelAt(gl_FragCoord.xy,0.,0.)) { //Surface position
gl_FragColor = vec4(backbuffer(0.,0.).rg+(backbuffer(4.,0.).r*(backbuffer(2.,0.).rg-backbuffer(1.,0.).rg)),0.,1.);
} else if (pixelAt(gl_FragCoord.xy,1.,0.)) { //New mouse position
gl_FragColor = vec4(iMouse.xy/iResolution.xy,0.,1.);
} else if (pixelAt(gl_FragCoord.xy,2.,0.)) { //Old mouse position
gl_FragColor = vec4(backbuffer(1.,0.).rg,0.,1.);
} else if (pixelAt(gl_FragCoord.xy,3.,0.)) { //New mouse holded
gl_FragColor = vec4(clamp(iMouse.z,0.,1.),0.,0.,1.);
} else if (pixelAt(gl_FragCoord.xy,4.,0.)) { //Old mouse holded
gl_FragColor = vec4(backbuffer(3.,0.).r,0.,0.,1.);
} else {
gl_FragColor = vec4(0.,0.,0.,1.);
}
}
`;
const BUFFER_FINAL_FRAG = `
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
varying vec2 vUv;
void main() {
vec2 uv = vUv;
vec2 a = texture2D(iChannel1,uv).xy;
gl_FragColor = vec4(texture2D(iChannel0,a).rgb,1.0);
}
`;
class App {
constructor() {
this.width = 1024;
this.height = 512;
this.renderer = new THREE.WebGLRenderer();
this.loader = new THREE.TextureLoader();
this.mousePosition = new THREE.Vector4();
this.orthoCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
this.counter = 0;
this.renderer.setSize(this.width, this.height);
document.body.appendChild(this.renderer.domElement);
this.renderer.domElement.addEventListener('mousedown', () => {
this.mousePosition.setZ(1);
this.counter = 0;
});
this.renderer.domElement.addEventListener('mouseup', () => {
this.mousePosition.setZ(0);
});
this.renderer.domElement.addEventListener('mousemove', event => {
this.mousePosition.setX(event.clientX);
this.mousePosition.setY(this.height - event.clientY);
});
this.targetA = new BufferManager(this.renderer, {
width: this.width,
height: this.height
});
this.targetB = new BufferManager(this.renderer, {
width: this.width,
height: this.height
});
this.targetC = new BufferManager(this.renderer, {
width: this.width,
height: this.height
});
}
start() {
const resolution = new THREE.Vector3(this.width, this.height, window.devicePixelRatio);
const channel0 = this.loader.load('https://res.cloudinary.com/di4jisedp/image/upload/v1523722553/wallpaper.jpg');
this.loader.setCrossOrigin('');
this.bufferA = new BufferShader(BUFFER_A_FRAG, {
iFrame: {
value: 0
},
iResolution: {
value: resolution
},
iMouse: {
value: this.mousePosition
},
iChannel0: {
value: null
},
iChannel1: {
value: null
}
});
this.bufferB = new BufferShader(BUFFER_B_FRAG, {
iFrame: {
value: 0
},
iResolution: {
value: resolution
},
iMouse: {
value: this.mousePosition
},
iChannel0: {
value: null
}
});
this.bufferImage = new BufferShader(BUFFER_FINAL_FRAG, {
iResolution: {
value: resolution
},
iMouse: {
value: this.mousePosition
},
iChannel0: {
value: channel0
},
iChannel1: {
value: null
}
});
this.animate();
}
animate() {
requestAnimationFrame(() => {
this.bufferA.uniforms['iFrame'].value = this.counter++;
this.bufferA.uniforms['iChannel0'].value = this.targetA.readBuffer.texture;
this.bufferA.uniforms['iChannel1'].value = this.targetB.readBuffer.texture;
this.targetA.render(this.bufferA.scene, this.orthoCamera);
this.bufferB.uniforms['iChannel0'].value = this.targetB.readBuffer.texture;
this.targetB.render(this.bufferB.scene, this.orthoCamera);
this.bufferImage.uniforms['iChannel1'].value = this.targetA.readBuffer.texture;
this.targetC.render(this.bufferImage.scene, this.orthoCamera, true);
this.animate();
});
}
}
class BufferShader {
constructor(fragmentShader, uniforms = {}) {
this.uniforms = uniforms;
this.material = new THREE.ShaderMaterial({
fragmentShader: fragmentShader,
vertexShader: VERTEX_SHADER,
uniforms: uniforms
});
this.scene = new THREE.Scene();
this.scene.add(
new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), this.material)
);
}
}
class BufferManager {
constructor(renderer, size) {
this.renderer = renderer;
this.readBuffer = new THREE.WebGLRenderTarget(size.width, size.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
type: THREE.FloatType,
stencilBuffer: false
});
this.writeBuffer = this.readBuffer.clone();
}
swap() {
const temp = this.readBuffer;
this.readBuffer = this.writeBuffer;
this.writeBuffer = temp;
}
render(scene, camera, toScreen = false) {
if (toScreen) {
this.renderer.render(scene, camera);
} else {
this.renderer.setRenderTarget(this.writeBuffer);
this.renderer.clear();
this.renderer.render(scene, camera)
this.renderer.setRenderTarget(null);
}
this.swap();
}
}
document.addEventListener('DOMContentLoaded', () => {
(new App()).start();
});