Прямоугольный / квадратный градиент в шейдере

#glsl #shader #webgl

#glsl #шейдер #webgl

Вопрос:

мой первый вопрос был закрыт. С тех пор я добавил немного кода и добился некоторого прогресса. Однако созданный градиент является круглым, и в настоящее время я не знаю, как преобразовать его в квадрат.

Вот мой текущий результат:

Целевой результат (в соответствии с этими строками):

Вот мой фрагментный шейдер:

 precision highp float;
        varying vec2 vTextureCoord;
        uniform float centerX;
        uniform float centerY;
        uniform vec4 colors[4];
        uniform float steps[4];

        void main() {
          vec3 map = vec3(vTextureCoord, 1);
          vec2 uv = map.xy;

          float dist = distance(vTextureCoord, vec2(centerX, centerY));
          highp vec4 col = colors[0];
          for (int i = 1; i < 4;   i) {
              col = mix(col, colors[i], smoothstep(steps[i - 1], steps[i], dist));
          }
          float factor = max(abs(uv.x - centerX), abs(uv.y - centerY));
          float c = 1. - max(abs(uv.x - centerX), abs(uv.y - centerY));
          vec4 finalColor = vec4((col.r - factor), (col.g - factor), (col.b - factor), 1.);

          gl_FragColor = finalColor;
        }  

Переданные параметры:

Цвета: [[1, 0, 0], [1, 1, 1], [1, 0, 0], [0, 1, 0]]

Шаги: [0, 0.29, 0.35, 1]

Комментарии:

1. Вместо того, чтобы использовать расстояние, используйте максимальное расстояние по оси. например, следующее получит максимальное расстояние от центра и нормализует его до значения 0-1 vec2 xyDist = abs(vTextureCoord - vec2(0.5,0.5)); float dist = max(xyDist.x, xyDist.y)/0.5; , которое затем можно использовать для интерполяции dist .

2. Можете ли вы привести мне пример использования centerX, centerY? Зачем делить на 0,5?

Ответ №1:

Использование текстурных координат

Ниже приведен пример, в котором используются координаты текстуры в диапазоне от 0 до 1, что делает центр градиента равным 0.5, 0.5 . Таким образом, для вычисления градиента мы должны нормализовать расстояние от центра из диапазона от 0 до 0,5 до 0-1. Это делается путем деления на 0,5 или обратного (как в примере) умножения на 2 (поскольку умножение всегда является лучшим вариантом, чем деление)

Упрощение шейдера

Кроме того, ваш метод вычисления цвета градиента для каждого фрагмента требует больших вычислительных затрат. Для каждого градиента (в данном случае 3) вы вызываете smoothstep and then mix , но для каждого фрагмента 2 из этих вычислений ничего не делают для вычисляемого цвета.

Приведенный ниже пример сокращает вычисления, проверяя, находится ли расстояние в пределах определенного градиента, и только если в пределах, затем вычисляет присвоение цвета gl_FragColor и затем выходит из цикла

Я не могу тренироваться, если вы хотите, чтобы градиент затемнялся до краев, как предполагают ваше первое изображение и код (и принятый ответ), или это второе изображение в вашем вопросе, которое является желаемым результатом. В примере предполагается, что вам нужно второе изображение.

 const shaders = {
vs: `attribute vec2 vert;
varying vec2 uv;
void main() {
    uv = (vert    1.0) / 2.0; // normalize texture coords
    gl_Position = vec4(vert, 0.0, 1.0);
}`, 
fs: `precision mediump float;
varying vec2 uv;  
uniform vec3 colors[4];
uniform float steps[4];
void main(){
    vec2 gradXY = abs(uv - 0.5);  // 0.5 is centerX, centerY
    float dist = pow(max(gradXY.x, gradXY.y) * 2.0, 2.0);
    float start = steps[0];
    for (int i = 1; i < 4; i  ) {
        float end = steps[i]; 
        if (dist >= start amp;amp; dist <= end) {
            gl_FragColor = vec4(mix(colors[i - 1], colors[i], (dist-start) / (end-start)), 1);
            break;
        }
        start = end;
    }
}`,};
const F32A = a => new Float32Array(a), UI16A = a => new Uint16Array(a);
const GLBuffer = (data, type = gl.ARRAY_BUFFER, use = gl.STATIC_DRAW, buf) => (gl.bindBuffer(type, buf = gl.createBuffer()), gl.bufferData(type, data, use), buf);
const GLLocs = (shr, type, ...names) => names.reduce((o,name) => (o[name] = (gl[`get${type}Location`])(shr, name), o), {});
const GLShader = (prg, source, type = gl.FRAGMENT_SHADER, shr) => {
    gl.shaderSource(shr = gl.createShader(type), source);
    gl.compileShader(shr);
    gl.attachShader(prg, shr);
}
var W;    
const gl = canvas.getContext("webgl");
requestAnimationFrame(render);
addEventListener("resize", render);
const prog = gl.createProgram();
GLShader(prog, shaders.vs, gl.VERTEX_SHADER);
GLShader(prog, shaders.fs);
gl.linkProgram(prog);
gl.useProgram(prog);
const locs = GLLocs(prog, "Uniform", "colors", "steps");
const vert = GLLocs(prog, "Attrib", "vert").vert;
GLBuffer(F32A([-1,-1, 1,-1, 1,1, -1,1]));
GLBuffer(UI16A([1,2,3, 0,1,3]), gl.ELEMENT_ARRAY_BUFFER);
gl.enableVertexAttribArray(vert);
gl.vertexAttribPointer(vert, 2, gl.FLOAT, false, 0, 0); 
function render() {
    gl.viewport(0, 0, W = canvas.width = Math.min(innerWidth,innerHeight), canvas.height = W);
    gl.uniform3fv(locs.colors, F32A([1,1,1,  1,0,0,  1,1,1,  0,0,0]));
    gl.uniform1fv(locs.steps, F32A([0, 1/3, 2/3, 1]));
    gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
}  
 body {
       margin: 0px;
    }
    canvas {
       position: absolute;
       top: 0px;
       left: 0px;
       background: black;
    }  
 <canvas id="canvas"></canvas>  

Неясности вопроса

В вашем вопросе есть ряд неясностей, которые рассматриваются в следующем

pow Функция в строке примера …

 float dist = pow(max(gradXY.x, gradXY.y) * 2.0, 2.0);
  

… является приближением к вашему использованию smoothstep(steps[i - 1], steps[i], dist) при вычислении col (при условии, что диапазон dist равен 0 — 0,5). Если вам нужна полная кривая Эрмита, вы можете заменить линию на …

 float distL = max(gradXY.x, gradXY.y) * 2.0;
float dist = distL * distL * (3.0 - 2.0 * distL);
  

.. и если вы хотите затемнить края, как на первом изображении вопросов, используйте следующую строку при вычислении цвета фрагмента. ПРИМЕЧАНИЕ, предполагая, что цвета vec4 не vec3 являются подходящими модами, если вы используете пример кода.

 FragColor = mix(colors[i - 1], colors[i], (dist-start) / (end-start)) - vec4(vec3(distL * 0.5),0);
  

или, если не использовать кривую Эрмита

 FragColor = mix(colors[i - 1], colors[i], (dist-start) / (end-start)) - vec4(vec3(dist * 0.5),0);
  

Комментарии:

1. Спасибо за ответ, он ответил на многие мои вопросы, которые я не задавал, поскольку мне нужно было продолжить работу над другими частями проекта. Ответ принят.

Ответ №2:

Квадратный градиент может быть достигнут путем вычисления максимального расстояния между обеими осями:

float dist = distance(vTextureCoord, vec2(centerX, centerY));

 vec2 distV = vTextureCoord - vec2(centerX, centerY);
float dist = max(abs(distV.x), abs(distV.y));
  

Полный пример:

 (function loadscene() {    

var canvas, gl, vp_size, prog, bufObj = {};

function initScene() {

    canvas = document.getElementById( "ogl-canvas");
    gl = canvas.getContext( "experimental-webgl" );
    if ( !gl )
      return;

    progDraw = gl.createProgram();
    for (let i = 0; i < 2;   i) {
        let source = document.getElementById(i==0 ? "draw-shader-vs" : "draw-shader-fs").text;
        let shaderObj = gl.createShader(i==0 ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
        gl.shaderSource(shaderObj, source);
        gl.compileShader(shaderObj);
        let status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
        if (!status) alert(gl.getShaderInfoLog(shaderObj));
        gl.attachShader(progDraw, shaderObj);
        gl.linkProgram(progDraw);
    }
    status = gl.getProgramParameter(progDraw, gl.LINK_STATUS);
    if ( !status ) alert(gl.getProgramInfoLog(progDraw));
    progDraw.inPos = gl.getAttribLocation(progDraw, "inPos");
    progDraw.u_time = gl.getUniformLocation(progDraw, "u_time");
    progDraw.u_resolution = gl.getUniformLocation(progDraw, "u_resolution");
    gl.useProgram(progDraw);

    var pos = [ -1, -1, 1, -1, 1, 1, -1, 1 ];
    var inx = [ 0, 1, 2, 0, 2, 3 ];
    bufObj.pos = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
    gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( pos ), gl.STATIC_DRAW );
    bufObj.inx = gl.createBuffer();
    bufObj.inx.len = inx.length;
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
    gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( inx ), gl.STATIC_DRAW );
    gl.enableVertexAttribArray( progDraw.inPos );
    gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 ); 
    
    gl.enable( gl.DEPTH_TEST );
    gl.clearColor( 0.0, 0.0, 0.0, 1.0 );

    window.onresize = resize;
    resize();
    requestAnimationFrame(render);
}

function resize() {
    //vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
    vp_size = [window.innerWidth, window.innerHeight];
    //vp_size = [256, 256]
    canvas.width = vp_size[0];
    canvas.height = vp_size[1];
}

function render(deltaMS) {

    gl.viewport( 0, 0, canvas.width, canvas.height );
    gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
   
    gl.uniform1f(progDraw.u_time, deltaMS/1000.0);
    gl.uniform2f(progDraw.u_resolution, canvas.width, canvas.height);
    gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
    
    requestAnimationFrame(render);
}  

initScene();

})();  
 <script id="draw-shader-vs" type="x-shader/x-vertex">
#version 100

attribute vec2 inPos;
varying vec2 ndcPos;

void main()
{
    ndcPos = inPos;
    gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>

<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;

varying vec2 ndcPos;  // normaliced device coordinates in range [-1.0, 1.0]
uniform float u_time;
uniform vec2 u_resolution;

#define FILL

void main()
{
    vec4 colors[4];
    colors[0] = vec4(1.0, 0.0, 0.0, 1.0);
    colors[1] = vec4(0.0, 1.0, 0.0, 1.0);
    colors[2] = vec4(0.0, 0.0, 1.0, 1.0);
    colors[3] = vec4(1.0, 1.0, 1.0, 1.0);

    float steps[4];
    steps[0] = 0.2;
    steps[1] = 0.4;
    steps[2] = 0.6;
    steps[3] = 0.8;

    vec2 uv = ndcPos.xy;
    uv.x *= u_resolution.x / u_resolution.y;
    
    vec2 vTextureCoord = uv;
    float centerX = 0.0;
    float centerY = 0.0;

    //float dist = distance(vTextureCoord, vec2(centerX, centerY));
    vec2 distV = vTextureCoord - vec2(centerX, centerY);
    float dist = max(abs(distV.x), abs(distV.y));
    
    highp vec4 col = colors[0];
    for (int i = 1; i < 4;   i) {
        col = mix(col, colors[i], smoothstep(steps[i - 1], steps[i], dist));
    }
    float factor = max(abs(uv.x - centerX), abs(uv.y - centerY));
    float c = 1. - max(abs(uv.x - centerX), abs(uv.y - centerY));
    vec4 finalColor = vec4((col.r - factor), (col.g - factor), (col.b - factor), 1.);

    gl_FragColor = finalColor;
}
</script>

<canvas id="ogl-canvas" style="border: none"></canvas>