着色器学校,顶点着色器:可变变量

shader-school, vertex shaders: varying variables

我目前正在使用 shader-school 学习一些图形编程/glsl,效果很好。但是我卡在了关于顶点着色器的第二课。

要求解决的三角形似乎显示了一个充满了YUV颜色不同色调的三角形space:

我知道可以计算 RGB 和 YUV 之间的转换,但是我不太确定顶点或片段着色器中的坐标 space 是如何工作的。

我的顶点着色器看起来像这样:

precision highp float;

attribute vec4 position;
attribute vec3 color;

varying vec4 fragPosition;
void main() {
  gl_Position = position;
  fragPosition = position; // send the position to the fragment shader
}

而我的片段着色器看起来像这样:

precision highp float;

varying vec4 fragPosition; // is set by vertex shader

void main() {
  gl_FragColor = fragPosition.rgba;
}

渲染的三角形看起来像这样(左边是要解决的问题,右边是我目前的解决方案):

fragPosition.a连续1.0;为什么褪色看起来很奇怪?三角形内部的坐标space到底是多少?我无法全神贯注,如果没有断点或打印语句,就不容易弄清楚。为什么左下角全黑,为什么没有蓝点?

opengl 的默认 space 是 NDC space。它是 [-1,1] 范围内的单位立方体。其中x=-1在屏幕左侧,x=1在屏幕右侧,y=-1底部,y=+1顶部,近平面z=-1,z=+1在远平面。

您现在在着色器中所做的基本上是将片段坐标显示为一种颜色。

三角形的顶部是绿色的,因为坐标类似于 [0,1,0,1],当解释为颜色时,它会呈现绿色。三角形右边的坐标为 [1,-1,0,1],因此呈现红色。底部很可能是 [0,-1,0,1] 之类的东西,它是黑色的。

顺便说一句,我不确定 fragment.a 是否真的是 1.0,你可以通过 gl_FragColor = fragPosition.aaaa; 来检查。如果 .a 是 1.0 那么你应该只能看到白色,如果 .a 是~0.5,它应该是灰色等等。

我在这里猜测,我认为你在本课中应该做的是使用颜色输入代替 (attribute vec3 color;) 并在着色器中实现 rgba 到 yuv 的转换。

我在这里看到 3 种不同的解决方案

首先是在 FS(片段着色器)中进行整个计算。例如,这种解决方案适用于后处理,但对性能影响很大。

设计思路: 您通过 VS(顶点着色器)仅传递白色三角形,需要一个属性,并根据位置在 FS 中处理所有颜色。

第二种方案是在VS中做颜色,在FS中使用插值得到结果。总体而言,此解决方案是最快的,但并非所有类型的问题都包含简单插值。

设计思路: 需要一种属性和一种变化。您将一个三角形的数据从 js 传递到 VS,在那里您在角落建立颜色并从插值颜色值获得 FS 的结果。

第三种解决方案是在JS中初始化颜色,将其传递给VS,每个角都有一种颜色,使用插值从FS得到结果。最后一个解决方案提供了高度模块化,并可能允许交互。成本是更长的代码并且可能更慢。

设计思路:两个属性一个varying,将positions和colorsYUV传给VS,然后只对colors做插值,其余结果在FS。

我对最后一个解决方案进行了简单编码,因为您可以从中学到最多的东西。

// Whosebug purposes

// initalize canvas and webgl
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");

gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;

// create shaders
var vsSource = `
        precision mediump float;

        attribute vec2 a_position;
        attribute vec3 a_color_yuv;

        varying vec3 v_color_yuv;
        
        void main() {
            v_color_yuv = a_color_yuv;
            gl_Position = vec4(a_position, 0., 1.);
        }
        `;

var fsSource = `
        precision mediump float;

        varying vec3 v_color_yuv; // is set by vertex shader

        void main() {
            float r = v_color_yuv.x + 1.4075 * (v_color_yuv.z);
            float g = v_color_yuv.x - 0.3455 * (v_color_yuv.y) - (0.7169 * (v_color_yuv.z));
            float b = v_color_yuv.x + 1.7790 * (v_color_yuv.y);
            
            gl_FragColor = vec4(r, g, b, 1.);
        }
        `;

var vs = gl.createShader(gl.VERTEX_SHADER);
var fs = gl.createShader(gl.FRAGMENT_SHADER);

// replace removes nonascii chars from shaders sources, because I created them with http://es6-features.org/#StringInterpolation
gl.shaderSource(vs, vsSource.replace(/[^\x00-\x7F]/g, ""));
gl.shaderSource(fs, fsSource.replace(/[^\x00-\x7F]/g, ""));

gl.compileShader(vs);
gl.compileShader(fs);

// create webgl program
var shaderProgram = gl.createProgram();

gl.attachShader(shaderProgram, vs);
gl.attachShader(shaderProgram, fs);

gl.linkProgram(shaderProgram);

gl.useProgram(shaderProgram);

// set uniform and attribute locations
var a_position = gl.getAttribLocation(shaderProgram, "a_position");
gl.enableVertexAttribArray(a_position);

var a_color_yuv = gl.getAttribLocation(shaderProgram, "a_color_yuv");
gl.enableVertexAttribArray(a_color_yuv);

// set attribute buffers
var trianglePositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, trianglePositionBuffer);
var positions = [
  -1.0, -1.0,
  1.0, -1.0,
  1.0, 1.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
trianglePositionBuffer.itemSize = 2;

var triangleColorYUVBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleColorYUVBuffer);
// modify this
var colorsYUV = [
  0.5, -0.5, -0.5,
  0.5, 0.0, 0.0,
  0.5, -0.5, 0.5
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colorsYUV), gl.STATIC_DRAW);
triangleColorYUVBuffer.itemSize = 3;

var bufferSize = 3;

// final draw
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);

gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

gl.bindBuffer(gl.ARRAY_BUFFER, trianglePositionBuffer);
gl.vertexAttribPointer(a_position, trianglePositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ARRAY_BUFFER, triangleColorYUVBuffer);
gl.vertexAttribPointer(a_color_yuv, triangleColorYUVBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.drawArrays(gl.TRIANGLES, 0, bufferSize);
<canvas id="c" style="border: none;" width="500" height="500"></canvas>

解释

fragPosition.a is continuously 1.0; why does the fading look so weird?

假设左图是YUV颜色,那么Y大概是0.5,因为右下角有灰色。

这跟褪色没有关系,但是你的Y一直是0,就是黑色,不是灰色。所以颜色更深。

How exactly does the coordinate space inside the triangle?

假设我使用范围 <0,1> 的 YUV 颜色用于 Y,<-0.5,0.5> 用于 U 和 V

让我们遍历三角形的所有角。

  • 从左下角开始。
  • 这个角的坐标是什么? -1, -1
  • 它有什么颜色?绿色
  • YUV 中的这个颜色是什么?好吧,Y 可能在 <0,1> 范围内的某个地方(但我猜它是 0.5),U 必须是 -0.5 并且 V 必须是 -0.5

接下来开始吧。右下角

  • 这个角的坐标是什么? 1, -1
  • 它有什么颜色?灰色
  • YUV 中的这个颜色是什么? U和V必须都为0,而Y为0.5

最后一个角,右上角:

  • 这个角的坐标是什么? 1, 1
  • 它有什么颜色?橙色
  • YUV 中的这个颜色是什么? U 必须是 -0.5 而 V 是 0.5 并且 Y 在 <0,1> 范围内(但我猜它是 0.5)

在您的着色器代码中,您试图将位置用作颜色。好吧,假设我们有 YUV 颜色。我们有 XY 位置。我们将 Y 设置为常量(你使用了 0,但它应该是 0.5)。但是然后 XY!=constant*UV + 常数(不管常数是什么)因为所需的三角形具有来自 YUV 颜色系统的扭曲颜色系统。左下角是绿色的,没问题。但在右下角是灰色而不是蓝色。右上角是橙色而不是紫色。左上角我们不知道,我们也不在乎。 因此,有两种解决方案。创建将为每个角分配颜色的颜色缓冲区或创建您自己的颜色系统(在片段着色器中)。我使用了第一个解决方案,所以我保留了经典的 YUV 系统。

Why is the bottom left corner completely black

你用坐标作为颜色,所以你的gl_FragColor.rgba=position.xyzw,也就是左下角的[-1, -1, 0, 1],会变成[0,0,0,1] = 黑色

why is there no blue spot

左图,作者可能不想要蓝色。在您的图片上,蓝色是您的 Z 坐标,整个时间为 0 = 任何地方都没有蓝色。