Проблема в том, что вам нужно использовать FLOAT
или, возможно, HALF_FLOAT
текстуры с вашими кадровыми буферами.
Обратите внимание, что возможность рендеринга в FLOAT
или HALF_FLOAT
является дополнительной функцией WebGL2, поэтомуВы должны проверить и включить либо EXT_color_buffer_float
, либо EXT_color_buffer_half_float
.
Фильтрация FLOAT
и HALF_FLOAT
текстур также является дополнительной функцией.Я понятия не имею, использует ли Shadertoy NEAREST
или LINEAR
.Я установил вещи на NEAREST
ниже.Если вы хотите использовать LINEAR
, вам также нужно проверить и включить либо OES_texture_float_linear
, либо OES_texture_half_float_linear
Если вам интересно, как я это выяснил ...
Сначала я убедился, что второй проход работает, добавив
fColor = vec4(p, 0, 1);
в нижнюю часть шейдера второго прохода.
Это сработало, так что я попробовал нечто похожее в шейдере первого прохода, добавив
fragColor = vec4(fragCoord / iResolution, 0, 1);
return;
Это сработало, так что я посмотрел на шейдер.Я видел, что это разделено на 3 части.Одна часть пропускает верхнюю строку, поэтому я помещаю туда fragColor = vec4(1,0,0,1)
просто как проверку работоспособности, чтобы убедиться, что она делает то, что я ожидал
Затем следующая часть выполняет симуляцию частиц, которую я пропустил, за исключением того, чтобы понять, что она просто используетнижний ряд для хранения.
Последняя часть делает размытие, поэтому я снова вставляю fragColor = vec4(0,1,0,1)
, чтобы убедиться, что область становится зеленой.Так оно и было.
Итак, учитывая, что оно размыто, я добавил код для ручной настройки текстуры, используя
gl.bindFramebuffer(gl.FRAMEBUFFER, rts[1].fbo);
gl.enable(gl.SCISSOR_TEST);
gl.scissor(100, 100, 10, 10);
gl.clearColor(.2, .5, .7, .9);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.disable(gl.SCISSOR_TEST);
Я пробовал это сделать как в цикле рендеринга, так и только во время инициализации.Когда я увидел, что маленький прямоугольник вообще не затронут, он подумал о том, в чем может быть проблема, и тогда я подумал, что, возможно, проблема в 8-битных целях рендера.Я проверил сайт shadertoy на наличие документов, но документы абсолютно ничего не говорят (очень плохие документы на shadertoy.com). Этот источник связан с shadertoy.com.Это приложение для iOS, а не веб-сайт, и поэтому я искал glTexImage2D
, чтобы посмотреть, не использовали ли они текстуры с плавающей запятой, как все.Ответ «да», поэтому я попробовал его и, по крайней мере, получил некоторые результаты, которые, казалось, решили проблему.
Несколько предложений по поводу кода
Если вы 'не помещая никаких данных в текстуру, нет причин передавать массив в gl.texImage2D
.Просто передайте null
.
Нет смысла использовать типизированный массив с gl.uniform, так что этот код
const resos = [width, height, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
gl.uniform3fv(iChannelResolutionPos, new Float32Array(resos));
может быть просто
const resos = [width, height, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
gl.uniform3fv(iChannelResolutionPos, resos);
gl.getShaderInfoLog
уже возвращает строку, поэтому
const info = String(gl.getShaderInfoLog(shader));
может быть просто
const info = gl.getShaderInfoLog(shader);
Код initProgram
не проверяет ошибки ссылок.
Ошибки ссылок могут возникать по любой причине.Наиболее распространенными являются несоответствия изменений, но в спецификации сказано, что компиляция шейдера всегда может быть успешной, если соединение не удастся, если шейдер потерпел неудачу при компиляции.
gl.linkProgram(prg);
const success = gl.getProgramParameter(gl.LINK_STATUS);
if (!success) {
console.log(gl.getProgramInfoLog(prg);
}
const fsSource = document.getElementById("shader-fs").text;
class RenderTarget {
constructor(gl, width, height) {
this.texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this.texture);
this.level = 0;
const internalFormat = gl.RGBA32F;
const border = 0;
const format = gl.RGBA;
const type = gl.FLOAT;
const data = null;
gl.texImage2D(gl.TEXTURE_2D, this.level, internalFormat, width, height, border, format, type, data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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);
this.fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
}
}
function loadShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
const info = String(gl.getShaderInfoLog(shader));
console.log(info, source);
gl.deleteShader(shader);
return info;
}
return shader;
}
function initProgram(gl, vsSource, fsSource) {
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
return shaderProgram;
}
function initQuad(gl) {
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = [-1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
return positionBuffer;
}
const vs = `#version 300 es
in vec2 aVertexPosition;
out vec2 uv;
void main() {
uv = aVertexPosition;
gl_Position = vec4(aVertexPosition, 0., 1.0);
}`;
const fs = `#version 300 es
precision highp float;
uniform sampler2D iChannel0;
out vec4 fColor;
void main() {
vec2 p = gl_FragCoord.xy / vec2(420., 320.);
fColor = texture(iChannel0, p);
}`;
function init() {
// Set up canvas and webgl2 context
const width = 420;
const height = 320;
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const gl = canvas.getContext("webgl2");
document.body.appendChild(canvas);
const ext = gl.getExtension('EXT_color_buffer_float');
if (!ext) {
return alert('need EXT_color_buffer_float');
}
// Compile shaders and set up two render targets
const prog1 = initProgram(gl, vs, fsSource);
const prog2 = initProgram(gl, vs, fs);
const rt1 = new RenderTarget(gl, width, height);
const rt2 = new RenderTarget(gl, width, height);
const rts = [rt1, rt2];
// Bind vertex quad
gl.useProgram(prog1);
const quadPos = gl.getAttribLocation(prog1, 'aVertexPosition');
const quad = initQuad(gl);
gl.bindBuffer(gl.ARRAY_BUFFER, quad);
gl.vertexAttribPointer(quadPos, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(quadPos);
gl.viewport(0, 0, width, height);
// Set up needed uniforms
const iTimePos = gl.getUniformLocation(prog1, "iTime");
const iTimeDeltaPos = gl.getUniformLocation(prog1, "iTimeDelta");
const iFramePos = gl.getUniformLocation(prog1, "iFrame");
const iResolutionPos = gl.getUniformLocation(prog1, "iResolution");
const iChannelResolutionPos = gl.getUniformLocation(prog1, "iChannelResolution");
const resos = [width, height, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
gl.uniform2f(iResolutionPos, width, height);
gl.uniform3fv(iChannelResolutionPos, new Float32Array(resos));
const prog1Channel0Pos = gl.getUniformLocation(prog1, "iChannel0");
gl.useProgram(prog2);
const prog2Channel0Pos = gl.getUniformLocation(prog2, "iChannel0");
let frame = 0;
let time = 0;
let lastTime = 0;
let setTexture = function(gl, tex, location, spot) {
gl.activeTexture(gl.TEXTURE0 + spot);
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.uniform1i(location, spot);
}
let animate = function() {
time = performance.now() / 1000;
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// FIRST RENDER PASS
gl.useProgram(prog1);
gl.uniform1f(iTimePos, time);
gl.uniform1i(iFramePos, frame);
gl.uniform1f(iTimeDeltaPos, time - lastTime);
gl.bindFramebuffer(gl.FRAMEBUFFER, rts[0].fbo);
gl.bindTexture(gl.TEXTURE_2D, rts[1].texture);
//setTexture(gl, rts[1].texture, prog1Channel0Pos, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
//SECOND RENDER PASS
gl.useProgram(prog2);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindTexture(gl.TEXTURE_2D, rts[0].texture);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
//setTexture(gl, rts[0].texture, prog2Channel0Pos, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
rts.reverse();
lastTime = time;
frame++;
requestAnimationFrame(animate);
}
lastTime = performance.now();
animate();
}
init();
<script type="x-shader/x-fragment" id="shader-fs" src="util/fs">#version 300 es
#ifdef GL_ES
precision highp float;
precision highp int;
precision mediump sampler3D;
#endif
uniform vec3 iChannelResolution[4];
uniform float iTime;
uniform float iTimeDelta;
uniform float timeDelta;
uniform vec2 iResolution;
uniform vec4 iMouse;
uniform int iFrame;
in vec2 uv;
out vec4 fColor;
uniform sampler2D iChannel0;
// The MIT License
// Copyright © 2018 Ian Reichert-Watts
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// SHARED PARAMS (Must be same as Image :/)
const int NUM_PARTICLES = 64;
const float INTERACT_DATA_INDEX = float(NUM_PARTICLES)+1.0;
const float KINETIC_MOUSE_INDEX = INTERACT_DATA_INDEX+1.0;
// SHARED FUNCTIONS (Must be same as Image :/)
vec4 loadData( in float index )
{
return texture( iChannel0, vec2((index+0.5)/iChannelResolution[0].x,0.0), -100.0 );
}
float floorHeight( in vec3 p )
{
return (sin(p.z*0.00042)*0.2)+(sin(p.z*0.008)*0.64) + (sin(p.x*0.42+sin(p.z*0.000042)*420.0))*0.42-1.0;
}
// PARAMS
const float PARTICLE_LIFETIME_MIN = 0.02;
const float PARTICLE_LIFETIME_MAX = 4.2;
const float FALL_SPEED = 42.0;
const float JITTER_SPEED = 300.0;
const vec3 WIND_DIR = vec3(0.0,0.0,-1.0);
const float WIND_INTENSITY = 4.2;
// CONST
const float PI = 3.14159;
const float TAU = PI * 2.0;
float randFloat( in float n )
{
return fract( sin( n*64.19 )*420.82 );
}
vec2 randVec2( in vec2 n )
{
return vec2(randFloat( n.x*12.95+n.y*43.72 ),randFloat( n.x*16.21+n.y*90.23 ));
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
if ( fragCoord.y > iResolution.y-2.0 )
{
// Discard top pixels to avoid persistent data getting included in blur
discard;
}
else if ( fragCoord.y < 2.0 )
{
if ( fragCoord.y >= 1.0 || fragCoord.x > float(NUM_PARTICLES+4) )
{
discard;
}
// Store persistent data in bottom pixel row
if ( fragCoord.x < float(NUM_PARTICLES) )
{
vec4 particle;
float pidx = floor(fragCoord.x);
if ( iFrame == 0 )
{
float padding = 0.01;
float particleStep = (1.0-(padding*2.0))/float(NUM_PARTICLES);
particle = vec4(0.0);
float r1 = randFloat(pidx);
particle.xy = vec2(padding+(particleStep*pidx), 1.0+(1.0*r1));
particle.xy *= iResolution.xy;
particle.a = r1*(PARTICLE_LIFETIME_MAX-PARTICLE_LIFETIME_MIN);
}
else
{
vec4 interactData = loadData(INTERACT_DATA_INDEX);
// Tick particles
particle = loadData(pidx);
vec2 puv = particle.xy / iResolution.x;
vec4 pbuf = texture(iChannel0, puv);
// Camera must be the same as Image :/
float rotYaw = -(interactData.x/iResolution.x)*TAU;
float rotPitch = (interactData.y/iResolution.y)*PI;
vec3 rayOrigin = vec3(0.0, 0.1, iTime*80.0);
float floorY = floorHeight(rayOrigin);
rayOrigin.y = floorY*0.9 + 0.2;
vec3 forward = normalize( vec3(sin(rotYaw), rotPitch, cos(rotYaw)) );
vec3 wup = normalize(vec3((floorY-floorHeight(rayOrigin+vec3(2.0,0.0,0.0)))*-0.2,1.0,0.0));
vec3 right = normalize( cross( forward, wup ) );
vec3 up = normalize( cross( right, forward ) );
mat3 camMat = mat3(right, up, forward);
vec3 surfforward = normalize( vec3(sin(rayOrigin.z*0.01)*0.042, ((floorY-floorHeight(rayOrigin+vec3(0.0,0.0,-20.0)))*0.2)+0.12, 1.0) );
vec3 wright = vec3(1.0,0.0,0.0);
mat3 surfMat = mat3(wright, up, surfforward);
vec2 centeredCoord = puv-vec2(0.5);
vec3 rayDir = normalize( surfMat*normalize( camMat*normalize( vec3(centeredCoord, 1.0) ) ) );
vec3 rayRight = normalize( cross( rayDir, up ) );
vec3 rayUp = normalize( cross( rayRight, rayDir ) );
// Wind
vec2 windShield = (puv-vec2(0.5, 0.0))*2.0;
float speedScale = 0.0015*(0.1+1.9*(sin(PI*0.5*pow( particle.z/particle.a, 2.0 ))))*iResolution.y;
particle.x += (windShield.x+WIND_INTENSITY*dot(rayRight, WIND_DIR))*FALL_SPEED*speedScale*iTimeDelta;
particle.y += (windShield.y+WIND_INTENSITY*dot(rayUp, WIND_DIR))*FALL_SPEED*speedScale*iTimeDelta;
// Jitter
particle.xy += 0.001*(randVec2( particle.xy+iTime )-vec2(0.5))*iResolution.y*JITTER_SPEED*iTimeDelta;
// Age
// Don't age as much when traveling over existing particle trails
particle.z += (1.0-pbuf.b)*iTimeDelta;
// Die of old age. Reset
if ( particle.z > particle.a )
{
float seedX = particle.x*25.36+particle.y*42.92;
float seedY = particle.x*16.78+particle.y*93.42;
particle = vec4(0.0);
particle.x = randFloat( seedX )*iResolution.x;
particle.y = randFloat( seedY )*iResolution.y;
particle.a = PARTICLE_LIFETIME_MIN+randFloat(pidx)*(PARTICLE_LIFETIME_MAX-PARTICLE_LIFETIME_MIN);
}
}
fragColor = particle;
}
else
{
float dataIndex = floor(fragCoord.x);
vec4 interactData = loadData(INTERACT_DATA_INDEX);
vec4 kineticMouse = loadData(KINETIC_MOUSE_INDEX);
if ( iMouse.z > 0.0 )
{
vec2 mouseDelta = iMouse.xy-kineticMouse.xy;
if ( length(iMouse.xy-iMouse.zw) < 4.0 )
{
mouseDelta = vec2(0.0);
}
interactData.xy += mouseDelta;
interactData.y = clamp( interactData.y, -iResolution.y, iResolution.y );
kineticMouse = vec4(iMouse.xy, mouseDelta);
}
else
{
kineticMouse.zw *= 0.9;
interactData.xy += kineticMouse.zw;
interactData.y = clamp( interactData.y, -iResolution.y, iResolution.y );
kineticMouse.xy = iMouse.xy;
}
fragColor = (dataIndex == KINETIC_MOUSE_INDEX) ? kineticMouse : interactData;
}
}
else
{
// Draw Particles
vec2 blurUV = fract( (fragCoord.xy + (fract( float(iFrame)*0.5 )*2.0-0.5)) / iResolution.xy );
vec2 uv = fragCoord.xy / iResolution.xy;
fragColor = texture( iChannel0, uv );
vec4 prevColor = fragColor;
if ( fragColor.a < 1.0 )
{
fragColor = texture( iChannel0, blurUV );
}
fragColor.b *= 0.996;
for ( int i=0; i<NUM_PARTICLES; i++ )
{
vec4 particle = loadData(float(i));
vec2 delta = fragCoord.xy-particle.xy;
float dist = length(delta);
float radius = 0.002*(0.5+2.0*particle.a+abs(sin(1.0*iTime+float(i))))*iResolution.y;
radius += 4.0*randFloat( particle.x*35.26+particle.y*93.12 )*pow((particle.z/particle.a), 12.0);
if ( dist < radius )
{
// normal
vec2 dir = delta/dist;
fragColor.r = dot(dir, vec2(1.0,0.0))*0.5+0.5;
fragColor.g = dot(dir, vec2(0.0,1.0))*0.5+0.5;
// height
float height = sin( dist/radius*PI*0.5 );
height = pow( height, 8.0 );
height = 1.0-height;
fragColor.b = max( height, prevColor.b );
// age
fragColor.a = 0.0;
}
}
fragColor.a += 0.1*iTimeDelta;
}
}void main() {
vec4 color = vec4(0.0,0.0,0.0,1.0);
mainImage(color, gl_FragCoord.xy);
color.w = 1.0;
fColor = color;
}
</script>