如何在 webgl 中使用 uv 纹理包裹空间以制作黑洞
Posted
技术标签:
【中文标题】如何在 webgl 中使用 uv 纹理包裹空间以制作黑洞【英文标题】:How to wrap space to make a black hole using uv textures in webgl 【发布时间】:2019-08-22 18:22:01 【问题描述】:我渲染了一个网格纹理。我想在片段着色器中操纵 uv 坐标(vQuadCoord
)以产生黑洞效果,即当线条接近中心时,线条之间的间隙会走得更远。也有圆形效果
我认为这是可能的,因为如果我这样做vQuadCoord = vQuadCoord * vQuadCoord
,它会达到类似的效果,但在角落里。
const fShaderSource = `#version 300 es
precision mediump float;
out vec4 outColor;
uniform sampler2D u_texture;
in vec2 vQuadCoord;
void main()
outColor = texture(u_texture, vQuadCoord);
`;
const vShaderSource = `#version 300 es
precision mediump float;
in vec2 a_position;
out vec2 vQuadCoord;
void main()
vQuadCoord = (a_position + 1.0) / 2.0;
gl_Position = vec4(a_position, 0, 1);
`;
main(document.getElementById('app'));
function main(element)
const canvas = document.createElement('canvas'),
gl = canvas.getContext('webgl2');
element.append(canvas);
const displayWidth = canvas.clientWidth,
displayHeight = canvas.clientHeight;
canvas.width = displayWidth;
canvas.height = displayHeight;
let graphics = new Graphics(width: displayWidth, height: displayHeight, gl);
new Loop(() =>
graphics.render();
).start();
function Graphics(state, gl)
const width, height = state;
gl.clearColor(0, 0, 0, 0);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
//gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
gl.enable(gl.BLEND);
gl.disable(gl.DEPTH_TEST);
let minibatch = [];
const redText = makeGlQuad(gl, fShaderSource, canvasTexture());
this.render = () =>
minibatch.push(redText);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT);
minibatch.forEach((
program,
resUniformLocation,
vao,
glTexture
) =>
gl.useProgram(program);
gl.uniform2f(resUniformLocation, gl.canvas.width, gl.canvas.height);
if (glTexture)
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, glTexture);
gl.bindVertexArray(vao);
gl.drawArrays(gl.TRIANGLES, 0, 6);
);
minibatch = [];
;
function makeGlQuad(gl, fShaderSource, texture)
let vShader = createShader(gl, gl.VERTEX_SHADER, vShaderSource);
let fShader = createShader(gl, gl.FRAGMENT_SHADER, fShaderSource);
let program = createProgram(gl, vShader, fShader);
let posAttrLocation = gl.getAttribLocation(program, "a_position");
let posBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
let left = -1,
right = 1,
down = -1,
up = 1;
/*
(-1, 1).( 1, 1)
.
(-1,-1).( 1,-1)
*/
let positions = [
left, down,
left, up,
right, down,
left, up,
right, down,
right, up
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
let vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(posAttrLocation);
let size = 2,
type = gl.FLOAT,
normalize = false,
stride = 0,
offset = 0;
gl.vertexAttribPointer(posAttrLocation,
size,
type,
normalize,
stride,
offset);
let glTexture;
if (texture)
glTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, glTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture);
//gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255]));
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
let resUniformLocation = gl.getUniformLocation(program, "u_resolution");
let texUniformLocation = gl.getUniformLocation(program, "u_texture");
return
program,
resUniformLocation,
vao,
glTexture
function canvasTexture()
return withCanvasTexture(256, 256, (w, h, canvas, ctx) =>
const gap = w * 0.07;
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, 10, 10);
ctx.strokeStyle = 'red';
ctx.lineWidth = 1;
ctx.beginPath();
for (let i = 0; i < w; i+= gap)
ctx.moveTo(i, 0);
ctx.lineTo(i, h);
for (let i = 0; i < h; i+= gap)
ctx.moveTo(0, i);
ctx.lineTo(w, i);
ctx.stroke();
return canvas;
);
function withCanvasTexture(width, height, f)
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
f(width, height, canvas, canvas.getContext('2d'));
const texture = canvas;
document.body.append(canvas);
return texture;
function createShader(gl, type, source)
let shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success)
return shader;
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
;
function createProgram(gl, vShader, fShader)
let program = gl.createProgram();
gl.attachShader(program, vShader);
gl.attachShader(program, fShader);
gl.linkProgram(program);
let success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success)
return program;
console.error(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
// Loop Library
function Loop(fn)
const perf = window.performance !== undefined ? window.performance : Date;
const now = () => perf.now();
const raf = window.requestAnimationFrame;
let running = false,
lastUpdate = now(),
frame = 0;
this.start = () =>
if (running)
return this;
running = true;
lastUpdate = now();
frame = raf(tick);
return this;
;
this.stop = () =>
running = false;
if (frame != 0)
raf.cancel(frame);
frame = 0;
return this;
;
const tick = () =>
frame = raf(tick);
const time = now();
const dt = time - lastUpdate;
fn(dt);
lastUpdate = time;
;
#app canvas
background: #ccc;
position: fixed;
top: 50%;
bottom: 0;
left: 50%;
right: 0;
width: 100vmin;
height: 70vmin;
transform: translate(-50%, -25%);
image-rendering: optimizeSpeed;
cursor: none;
margin: auto;
<div id="app">
</div>
【问题讨论】:
还有为什么纹理会翻转? 不确定是否有帮助我前段时间创建了一个着色器,我把它留在这里shadertoy.com/view/MlByzd 【参考方案1】:[...] 在片段着色器中制作黑洞效果,即当线条接近中心时,线条之间的间隙会变得更远。
您必须执行(1.0 - (1.0 - abs(x)) * (1.0 - abs(x)))
之类的操作。 x
是一个坐标,其中 (0,0) 位于纹理的中心。
将纹理坐标从 [0, 1] 范围转换为 [-1, 1] 范围:
vec2 p = vQuadCoord * 2.0 - 1.0;
计算“黑洞效应”坐标:
p = sign(p) * (1.0 - (1.0 - abs(p)) * (1.0 - abs(p)));
从范围 [-1, 1] 转换回 [0, 1]:
vec2 uv = p * 0.5 + 0.5;
对于圆形效果,您必须将归一化方向矢量乘以一个因子,该因子取决于到中心的平方距离或到边界的距离:
p = normalize(p) * length(p) * length(p);
或
p = normalize(p) * (1.0 - (1.0 - length(p)) * (1.0 - length(p)))
片段着色器:
precision mediump float;
out vec4 outColor;
uniform sampler2D u_texture;
in vec2 vQuadCoord;
void main()
vec2 p = vQuadCoord * 2.0 - 1.0;
//p = sign(p) * (1.0 - (1.0 - abs(p)) * (1.0 - abs(p)));
//p = normalize(p) * (1.0 - (1.0 - length(p)) * (1.0 - length(p)));
p = normalize(p) * length(p) * length(p);
vec2 uv = p * 0.5 + 0.5;
outColor = texture(u_texture, uv);
要对纹理进行 y 翻转,您可以设置 UNPACK_FLIP_Y_WEBGL
标志。见WebGL 2.0, 5.14.8 Texture objects:
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture);
【讨论】:
实际上我希望线条循环。也就是说,当线条到中心的距离相等时,间隙应该相同。这样做的问题是,角落与边缘的间隙相同,但我认为角落离中心更远, 您能否分享您的信息来源,例如您如何知道导致这种情况的原因。 @eguneys 对不起,但来源是我的大脑。那只是数学。我知道这样的事情,因为坐标转换和 glsl 是我日常工作的一部分。 @eguneys 您可能对Issue getting gradient square in glsl es 2.0, Gamemaker Studio 2.0 或Coloring rectangle in function of distance to nearest edge produces weird result in diagonals 的答案感兴趣。 你将如何控制失真的影响,就像我将影响设置为零将没有效果,如果我将其设置为一,效果将是可见的,介于两者之间的一切都会被插值以上是关于如何在 webgl 中使用 uv 纹理包裹空间以制作黑洞的主要内容,如果未能解决你的问题,请参考以下文章
深入理解Three.js(WebGL)贴图(纹理映射)和UV映射