【发布时间】:2019-08-07 09:41:18
【问题描述】:
在互联网上的许多示例(例如webglfundamentals 或webgl-bolerplate)中,作者使用两个三角形覆盖全屏并为画布上的每个像素调用像素着色器。
var canvas, gl, buffer,
vertex_shader, fragment_shader,
currentProgram, vertex_position,
timeLocation, resolutionLocation,
parameters = { start_time : new Date().getTime(),
time : 0,
screenWidth : 0,
screenHeight: 0 };
init();
animate();
function init() {
vertex_shader = document.getElementById('vs').textContent;
fragment_shader = document.getElementById('fs').textContent;
canvas = document.querySelector( 'canvas' );
try {
gl = canvas.getContext( 'experimental-webgl' );
} catch( error ) { }
if ( !gl )
throw "cannot create webgl context";
// Create Vertex buffer (2 triangles)
buffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ - 1.0, - 1.0, 1.0, - 1.0, - 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, - 1.0, 1.0 ] ), gl.STATIC_DRAW );
currentProgram = createProgram( vertex_shader, fragment_shader );
timeLocation = gl.getUniformLocation( currentProgram, 'time' );
resolutionLocation = gl.getUniformLocation( currentProgram, 'resolution' );
}
function createProgram( vertex, fragment ) {
var program = gl.createProgram();
var vs = createShader( vertex, gl.VERTEX_SHADER );
var fs = createShader( '#ifdef GL_ES\nprecision highp float;\n#endif\n\n' + fragment, gl.FRAGMENT_SHADER );
if ( vs == null || fs == null )
return null;
gl.attachShader( program, vs );
gl.attachShader( program, fs );
gl.deleteShader( vs );
gl.deleteShader( fs );
gl.linkProgram( program );
if ( !gl.getProgramParameter( program, gl.LINK_STATUS ) ) {
alert( "ERROR:\n" +
"VALIDATE_STATUS: " + gl.getProgramParameter( program, gl.VALIDATE_STATUS ) + "\n" +
"ERROR: " + gl.getError() + "\n\n" +
"- Vertex Shader -\n" + vertex + "\n\n" +
"- Fragment Shader -\n" + fragment );
return null;
}
return program;
}
function createShader( src, type ) {
var shader = gl.createShader( type );
gl.shaderSource( shader, src );
gl.compileShader( shader );
if ( !gl.getShaderParameter( shader, gl.COMPILE_STATUS ) ) {
alert( ( type == gl.VERTEX_SHADER ? "VERTEX" : "FRAGMENT" ) + " SHADER:\n" + gl.getShaderInfoLog( shader ) );
return null;
}
return shader;
}
function resizeCanvas( event ) {
if ( canvas.width != canvas.clientWidth ||
canvas.height != canvas.clientHeight ) {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
parameters.screenWidth = canvas.width;
parameters.screenHeight = canvas.height;
gl.viewport( 0, 0, canvas.width, canvas.height );
}
}
function animate() {
resizeCanvas();
render();
requestAnimationFrame( animate );
}
function render() {
if ( !currentProgram )
return;
parameters.time = new Date().getTime() - parameters.start_time;
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
gl.useProgram( currentProgram );
gl.uniform1f( timeLocation, parameters.time / 1000 );
gl.uniform2f( resolutionLocation, parameters.screenWidth, parameters.screenHeight );
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.vertexAttribPointer( vertex_position, 2, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vertex_position );
gl.drawArrays( gl.TRIANGLES, 0, 6 );
gl.disableVertexAttribArray( vertex_position );
}
html, body {
background-color: #000000;
margin: 0px;
overflow: hidden;
width: 100%;
height: 100%;
}
canvas {
width: 100%;
height: 100%;
}
<canvas></canvas>
<div id="info"></div>
<script id="vs" type="x-shader/vertex">
attribute vec3 position;
void main() {
gl_Position = vec4( position, 1.0 );
}
</script>
<script id="fs" type="x-shader/fragment">
uniform float time;
uniform vec2 resolution;
void main( void ) {
vec2 position = - 1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
float red = abs( sin( position.x * position.y + time / 5.0 ) );
float green = abs( sin( position.x * position.y + time / 4.0 ) );
float blue = abs( sin( position.x * position.y + time / 3.0 ) );
gl_FragColor = vec4( red, green, blue, 1.0 );
}
</script>
此代码使用具有 6 个顶点的缓冲区来呈现如下内容:
这种方法有什么好处吗?
与方法相比,我们渲染一个三角形(3个顶点)覆盖全屏,如下图所示:
body{
margin: 0;
overflow: hidden;
}
<canvas></canvas>
<script type='glsl/vertex'>
attribute vec2 coords;
void main(void) {
gl_Position = vec4(coords.xy, 0.0, 1.0);
}
</script>
<script type='glsl/fragment'>precision highp float;
uniform vec4 mr;
void main(void) {
vec2 p = gl_FragCoord.xy;
vec2 q = (p + p - mr.ba) / mr.b;
for(int i = 0; i < 13; i++) {
q = abs(q)/dot(q,q) - mr.xy/mr.zw;
}
gl_FragColor = vec4(q, q.x/q.y, 1.0);
}
</script>
<script>
let canvas = document.querySelector('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
var h = gl.drawingBufferHeight;
var w = gl.drawingBufferWidth;
let pid = gl.createProgram();
shader('glsl/vertex', gl.VERTEX_SHADER);
shader('glsl/fragment', gl.FRAGMENT_SHADER);
gl.linkProgram(pid);
gl.useProgram(pid);
let array = new Float32Array([-1, 3, -1, -1, 3, -1]);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);
let al = gl.getAttribLocation(pid, "coords");
gl.vertexAttribPointer(al, 2 /*components per vertex */, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(al);
let mr = gl.getUniformLocation(pid, 'mr');
window.addEventListener('mousemove', draw);
window.addEventListener('touchmove', draw);
draw();
function draw(e) {
let ev = e && e.touches ? e.touches[0] : e;
let x = ev ? ev.clientX : 250;
let y = ev ? h - ev.clientY: 111;
gl.uniform4f(mr, x, y, w, h);
gl.viewport(0, 0, w, h);
gl.clearColor(0, 0, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
function shader(name, type) {
let src = [].slice.call(document.scripts).find(s => s.type === name).innerText;
let sid = gl.createShader(type);
gl.shaderSource(sid, src);
gl.compileShader(sid);
gl.attachShader(pid, sid);
}
</script>
【问题讨论】:
-
没有重要区别。无论如何,您正在绘制单屏四边形。这完全是一种偏好。我认为画你想画的画是正常的,而不是依赖副作用。例如,我可以通过使用正交投影绘制球体和圆柱体来绘制圆形和矩形,但我没有。如果我想画一个圆或一个矩形,我提供一个圆或一个矩形的数据,而不是一个球体和圆柱体。同样,如果我想绘制一个四边形,我会给出一个四边形的数据。我认为三角形方法只是棘手的一种方式。但这真的没关系。随心所欲。
-
@gman 我考虑三角形边缘上的像素,gpu 是否单独处理三角形,是否每个三角形或每个四边形调用光栅化?
-
@rabbit76,不是他们没有画两次。这将明显违反规范,并且还会在混合打开时出现边缘。至于光栅化,这取决于驱动程序和 GPU。例如,平铺的 GPU 将事物存储到平铺中并分别绘制每个平铺。渲染图块被并行化只是一个例子。同样取决于 GPU,它只是将三角形剪辑成一个四边形,然后将四边形细分为 2 个三角形。这肯定是最简单的光栅化方法。让硬件快速做一件事(画三角形),然后把所有东西都变成三角形。
-
@gman : 你的 cmets 听起来不像是答案,而不仅仅是澄清吗?
-
这是我用一个有 4 个顶点的扇子来绘制我的 ndc 四边形 ;)
标签: javascript webgl fragment-shader