searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

网页中的GPU渲染

2023-05-15 05:21:31
80
0
一、GPU渲染为什么高效?
 
计算机渲染图像其实就是给屏幕上的一个个像素点着色,哪怕是对于一个老式的屏幕(分辨率 800x600)来说,都需要每帧处理480000个像素,如果帧频是30,即每秒进行28800000次计算。CPU可以想象成一个大的工业管道,它处理单个任务的能力很强,但需要串行的处理任务,即一个一个的处理。对于图像渲染这个任务来说,它的效率很低。
 
 
GPU可以想象成很多个小的工业管道,它处理单个任务的能力很弱,但可以并行的处理任务,即一次处理很多任务。对于图像渲染来说,这种处理任务的方式非常高效,因为图像渲染就是给像素点着色,不需要很强的处理单个任务的能力,而是需要并行处理任务的能力。
 
二、GPU渲染流水线
 
1、顶点着色器(Vertex Shader):接收来自JavaScript传入的顶点数据。
 
2、图元装配:简单的说就是将顶点着色器最终计算的顶点坐标连线,组成点、线、面等几何图元。具体是画哪种图元是通过JavaScript传入的图元信息决定的。webGL能画的图元只有点、直线段、三角形三种,图元装配环节就是将复杂的图形分割成一个个的点、直线段、三角形。
 
3、光栅化:图元装配出来的几何图形被打散成一个个的像素,这个过程叫光栅化。光栅化后的像素才会被片段着色器着色。
 
4、片段着色器(Fragment Shader):可以说是GPU渲染的核心,片段着色器是一系列指令,这些指令会对屏幕上的每个光栅化后的像素同时下达。也就是说,你的代码必须根据像素在屏幕上的不同位置执行不同的操作。就像前面所诉的一系列小的工业管道,你的程序就像一个 function(函数),输入位置信息,输出颜色信息,当它编译完之后会以相当快的速度运行。
 
 
三、 网页中的GPU渲染-webGL
 
在前端开发中,如何使用GPU渲染图像?在开启了硬件加速的浏览器中,html元素的渲染就是使用的GPU渲染,作为前端工程师的你,啥也不用做。如果想自己控制GPU渲染的过程,需要使用到webGL技术。webGL是浏览器提供的一套图形渲染接口,它是在GPU中渲染图形的。直接使用webGL接口是非常繁琐的,好在有很多大佬开发出了各种库来简化webGL接口的调用,它们的侧重点各不同,本文选择一个轻量级的库twgl.js来做些简单的例子。
 
四、webGL程序的工作流程
 
1、canvas与webGL上下文
 
首先需要一个画布,作为前端工程师的你,一定想到了canvas标签。然后获取图形接口上下文,通常情况下,我们会传入参数"2d",但这次我们传的是"webgl",它获取的是webgl上下文。
 
<canvas id="canvas" style="width: 500px; height: 500px;"></canvas>
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');

 

2、顶点着色器、片段着色器
 
GPU渲染流水线中能编程的就是顶点着色器和片段着色器,webgl中使用的着色器代码是使用GLSL ES编写的,它的风格很像C语言,还是比较容易看懂的。
 
顶点着色器的例子:
attribute vec3 position;

void main() {
	gl_Position = vec4(position, 1.0);
}
顶点着色器都有一个入口main函数,是程序开始执行的地方。上面的代码将位置向量position传给webGL系统的内置变量gl_Position,该变量表示webGL系统的顶点位置信息。顶点着色器执行后,就会进入GPU渲染流水线的图元装配、光栅化环节,然后光栅化后的像素会使用片段着色器着色。
 
片段着色器的例子:
uniform vec2 iResolution;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;
	gl_FragColor = vec4(st.x, 0.0, 0.0, 1.0);
}
片段着色器代码从main函数开始执行,首先获取当前像素的坐标,然后给该像素着色。gl_FragCoord是webGL系统的内置变量,它表示当前的像素坐标,iResolution是用户传入的canvas的宽高向量,gl_FragCoord.xy / iResolution是为了将当前像素坐标归一化为0到1之间。gl_FragColor也是webGL系统的内置变量,它表示当前像素的颜色向量,颜色由红、绿、蓝、alpha这四个通道表示,和CSS里的表示一样,这里把当前像素的红色通道设置为归一化的x坐标,其他通道都写死。
 
3、Javascript扮演的角色
 
webGL通过执行GESL ES代码编写的着色器来渲染图形,那么Javascript是用来干什么的呢?Javascript起到调度作用,它通过调用webGL接口来编译着色器、给着色器传参、控制渲染的执行时机等等。下面的Javascript代码使用了twgl.js来调用webGL接口来编译着色器,给顶点着色器传递顶点数据(arrays),给片段着色器传递canvas元素的宽高(iResolution),并且在requestAnimationFrame函数回调中执行渲染。
 
import {
	resizeCanvasToDisplaySize,
	createProgramInfo,
	createBufferInfoFromArrays,
	setBuffersAndAttributes,
	setUniforms,
	drawBufferInfo,
} from 'twgl.js';

import glslify from 'glslify';

import vShaderSource from '../../shaders/common.vs';
import fShaderSource from './index.fs';

const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');

const programInfo = createProgramInfo(gl, [glslify(vShaderSource), glslify(fShaderSource)]);

/**
 * a --- b
 * |  \  |
 * c --- d
 */
const arrays = {
	position: {
		numComponents: 3,
		data: [
			-1, -1, 0, // c
			1, -1, 0,  // d
			-1, 1, 0,  // a

			-1, 1, 0,  // a
			1, -1, 0,  // d
			1, 1, 0    // b
		],
	},
};
const bufferInfo = createBufferInfoFromArrays(gl, arrays);

function render(time) {
	resizeCanvasToDisplaySize(canvas);
	gl.viewport(0, 0, canvas.width, canvas.height);
	gl.useProgram(programInfo.program);

	setBuffersAndAttributes(gl, programInfo, bufferInfo);
	
	const uniforms = {
		iResolution: [
			canvas.width,
			canvas.height,
		],
	};
	setUniforms(programInfo, uniforms);
	
	drawBufferInfo(gl, bufferInfo);
}
function renderLoop(time) {
	render(time);
	requestAnimationFrame(renderLoop);
}
requestAnimationFrame(renderLoop);

最终渲染出了如下图像:

渲染的核心还是在片段着色器,所以其他的代码不用看,直接看片段着色器就能很好理解为什么会渲染成上图的样子。因为本文例子中canvas的每个像素都被光栅化了,所以为了简便起见,下面的例子都不用管Javascript代码和顶点着色器代码,直接看片段着色器代码即可看懂渲染的原理。
 
四、绘制图像及产生动画
 
1、绘制矩形
 
首先来认识step函数,它是GLSL内置的一个函数,第一个参数是阈值,第二个参数的值如果小于阈值就会返回0,否则返回1,我们可以利用它来绘制矩形:
 
uniform vec2 iResolution;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;
	float l = step(0.2, st.x);
	float r = step(0.2, 1.0 - st.x);
	float b = step(0.2, st.y);
	float t = step(0.2, 1.0 - st.y);
	float pct = l * r * b * t;
	gl_FragColor = vec4(vec3(pct), 1.0);
}
它会渲染成下面的图像:
 
 
再认识一个内置函数smoothstep,和step函数不同的地方在于,它由两个阈值,阈值之间是一个线性过渡效果,比如把step函数换成smoothstep。
 
float l = smoothstep(0.15, 0.25, st.x);
float r = smoothstep(0.15, 0.25, 1.0 - st.x);
float b = smoothstep(0.15, 0.25, st.y);
float t = smoothstep(0.15, 0.25, 1.0 - st.y);
float pct = l * r * b * t;
会渲染成下面的图像:
 
 
接下来我们将渲染矩形的代码提取为一个函数放到另外一个模块里:
 
/**
* @desc 画矩形模块
* @param w {float} 宽
* @param h {float} 高
* @param x {float} x轴偏移量
* @param y {float} y轴偏移量
* @param st {vec2} 画布坐标
* @param smoothThreshold {float} 羽化程度
* @return {float} 颜色分量0.0到1.0之间
*/
float rect(float w, float h, float x, float y, vec2 st, float smoothThreshold) {
	vec2 bl, tr,
		blDistance = vec2(x, y),
		trDistance = vec2(1.0 - x - w, 1.0 - y - h);
	if (smoothThreshold > 0.0) {
		bl = smoothstep(
			blDistance - vec2(smoothThreshold),
			blDistance + vec2(smoothThreshold),
			st
		);
		tr = smoothstep(
			trDistance - vec2(smoothThreshold),
			trDistance + vec2(smoothThreshold),
			1.0 - st
		);
	} else {
		bl = step(blDistance, st);
		tr = step(trDistance, 1.0 - st);
	}
	return bl.x * bl.y * tr.x * tr.y;
}

#pragma glslify: export(rect)
 
这样我们可以导入该模块来绘制矩形:
 
#pragma glslify: rect = require(../../shaders/rect.glsl);
uniform vec2 iResolution;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;
	float pct = rect(0.5, 0.5, 0.25, 0.25, st, 0.0);
	gl_FragColor = vec4(vec3(pct), 1.0);
}

2、加入动画

前面Javascript代码中我们将时间戳参数传给了着色器,可以利用它实现动画:

#pragma glslify: rect = require(../../shaders/rect.glsl);

uniform vec2 iResolution;
uniform float iTime;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;
	float pct = rect(0.5, 0.5, abs(sin(iTime)) * 0.5, 0.25, st, 0.0);
	gl_FragColor = vec4(vec3(pct), 1.0);
}

 

五、贴图,模拟photoshop图层混合效果
 
我们知道片元着色器能够控制光栅化后的每个像素的颜色,意味着我们可以绘制出任意复杂的图像,比如画一副风景图,但这是吃力不讨好的行为,有更好的方法来做,就是使用纹理贴图。我们把一幅准备好的风景图作为纹理贴到webGL绘制的图形上就可以了。纹理贴图的原理很简单:向片元着色器提供纹理图像(采样器)和纹理坐标,然后在片元着色器的逐像素操作中,将纹理坐标映射的纹理图像的像素颜色(纹素)提取出来,赋值给光栅化后的像素。
 
片段着色器很简单,传入了纹理图像(iChannel0)和纹理坐标(iResolution),并在入口函数里完成了纹理采样的过程。texture2D函数是GLSL ES内置函数,用于2D纹理的采样。
 
uniform vec2 iResolution;
uniform sampler2D iChannel0;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;
	gl_FragColor = texture2D(iChannel0, st);
}

上面的例子只是贴一张纹理图,我们也可以贴多张纹理图,并通过一定的算法混合多幅图像,得到特殊的效果。比如photoshop里面图层的混合方式,正片叠底、滤色、线性加深(减淡)等等,都可以实现。其实这很简单,我们把片段着色器改改:

uniform vec2 iResolution;
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;
	vec4 texture0 = texture2D(iChannel0, st);
	vec4 texture1 = texture2D(iChannel1, st);
	// 正片叠底
	vec4 color = texture0 * texture1;
	// 滤色
	// vec4 color = 1.0 - (1.0 - texture0) * (1.0 - texture1);
	// 线性减淡
	// vec4 color = texture0 + texture1;
	// 线性加深
	// vec4 color = texture0 + texture1 - 1.0;

	gl_FragColor = color;
}

六、模拟自然效果-噪声
 
片段着色器有强大的造型能力,可以模拟自然界中的一些效果,比如火焰、金属、木头纹理等等。下面的片段着色器使用第三方噪声模块产生木头纹理的效果:
 
#pragma glslify: snoise2 = require(glsl-noise/simplex/2d)

// 圆周率
#define PI 3.14159265359

/**
* @desc 2d旋转函数
* @param {float} angle 旋转弧度
* @return {mat2} 旋转矩阵
*/
mat2 rotate2d(float angle){
	return mat2(
		cos(angle), -sin(angle),
		sin(angle), cos(angle)
	);
}

/**
* @desc 画横线
* @param {vec2} pos 坐标
* @param {float} b 线的宽度
* @return {float} 0.0到1.0的权重值
*/
float lines(in vec2 pos, float b){
	float scale = 10.0;
	pos *= scale;
	return smoothstep(
		0.0,
		0.5 + b * 0.5,
		abs((sin(pos.y * PI) + b * 2.0)) * 0.5
	);
}

/**
* @desc 画木头纹理
* @param {vec2} st 坐标
* @return {float} 0.0到1.0的权重值
*/
float drawWoodTexture(in vec2 st, in float time) {
	vec2 pos = st * vec2(2.0, 2.0);
	// pos = rotate2d(snoise2(pos)) * pos;
	pos = rotate2d(snoise2(pos) * abs(sin(time / 10.0))) * pos;
	float pct = lines(pos, 0.2);
	return pct;
}

uniform vec2 iResolution;
uniform float iTime;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;

	float pct = drawWoodTexture(st, iTime);
	vec3 color = vec3(pct);

	gl_FragColor = vec4(color, 1.0);
}

 

七、使用第三方模块
 
除了自己去实现一些片段着色器,也可以导入第三方模块来使用。下面的例子导入第三方的rain模块来实现涟漪的效果。
 
precision highp float;

#pragma glslify: testRain = require(./rain.glsl)

uniform vec2 iResolution;
uniform float iTime;
uniform vec2 iMouse;
uniform sampler2D iChannel0;

void main() {
	testRain(gl_FragColor, gl_FragCoord.xy, iResolution, iTime, iMouse, iChannel0);
}

 

0条评论
作者已关闭评论
周****卿
2文章数
0粉丝数
周****卿
2 文章 | 0 粉丝
周****卿
2文章数
0粉丝数
周****卿
2 文章 | 0 粉丝
原创

网页中的GPU渲染

2023-05-15 05:21:31
80
0
一、GPU渲染为什么高效?
 
计算机渲染图像其实就是给屏幕上的一个个像素点着色,哪怕是对于一个老式的屏幕(分辨率 800x600)来说,都需要每帧处理480000个像素,如果帧频是30,即每秒进行28800000次计算。CPU可以想象成一个大的工业管道,它处理单个任务的能力很强,但需要串行的处理任务,即一个一个的处理。对于图像渲染这个任务来说,它的效率很低。
 
 
GPU可以想象成很多个小的工业管道,它处理单个任务的能力很弱,但可以并行的处理任务,即一次处理很多任务。对于图像渲染来说,这种处理任务的方式非常高效,因为图像渲染就是给像素点着色,不需要很强的处理单个任务的能力,而是需要并行处理任务的能力。
 
二、GPU渲染流水线
 
1、顶点着色器(Vertex Shader):接收来自JavaScript传入的顶点数据。
 
2、图元装配:简单的说就是将顶点着色器最终计算的顶点坐标连线,组成点、线、面等几何图元。具体是画哪种图元是通过JavaScript传入的图元信息决定的。webGL能画的图元只有点、直线段、三角形三种,图元装配环节就是将复杂的图形分割成一个个的点、直线段、三角形。
 
3、光栅化:图元装配出来的几何图形被打散成一个个的像素,这个过程叫光栅化。光栅化后的像素才会被片段着色器着色。
 
4、片段着色器(Fragment Shader):可以说是GPU渲染的核心,片段着色器是一系列指令,这些指令会对屏幕上的每个光栅化后的像素同时下达。也就是说,你的代码必须根据像素在屏幕上的不同位置执行不同的操作。就像前面所诉的一系列小的工业管道,你的程序就像一个 function(函数),输入位置信息,输出颜色信息,当它编译完之后会以相当快的速度运行。
 
 
三、 网页中的GPU渲染-webGL
 
在前端开发中,如何使用GPU渲染图像?在开启了硬件加速的浏览器中,html元素的渲染就是使用的GPU渲染,作为前端工程师的你,啥也不用做。如果想自己控制GPU渲染的过程,需要使用到webGL技术。webGL是浏览器提供的一套图形渲染接口,它是在GPU中渲染图形的。直接使用webGL接口是非常繁琐的,好在有很多大佬开发出了各种库来简化webGL接口的调用,它们的侧重点各不同,本文选择一个轻量级的库twgl.js来做些简单的例子。
 
四、webGL程序的工作流程
 
1、canvas与webGL上下文
 
首先需要一个画布,作为前端工程师的你,一定想到了canvas标签。然后获取图形接口上下文,通常情况下,我们会传入参数"2d",但这次我们传的是"webgl",它获取的是webgl上下文。
 
<canvas id="canvas" style="width: 500px; height: 500px;"></canvas>
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');

 

2、顶点着色器、片段着色器
 
GPU渲染流水线中能编程的就是顶点着色器和片段着色器,webgl中使用的着色器代码是使用GLSL ES编写的,它的风格很像C语言,还是比较容易看懂的。
 
顶点着色器的例子:
attribute vec3 position;

void main() {
	gl_Position = vec4(position, 1.0);
}
顶点着色器都有一个入口main函数,是程序开始执行的地方。上面的代码将位置向量position传给webGL系统的内置变量gl_Position,该变量表示webGL系统的顶点位置信息。顶点着色器执行后,就会进入GPU渲染流水线的图元装配、光栅化环节,然后光栅化后的像素会使用片段着色器着色。
 
片段着色器的例子:
uniform vec2 iResolution;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;
	gl_FragColor = vec4(st.x, 0.0, 0.0, 1.0);
}
片段着色器代码从main函数开始执行,首先获取当前像素的坐标,然后给该像素着色。gl_FragCoord是webGL系统的内置变量,它表示当前的像素坐标,iResolution是用户传入的canvas的宽高向量,gl_FragCoord.xy / iResolution是为了将当前像素坐标归一化为0到1之间。gl_FragColor也是webGL系统的内置变量,它表示当前像素的颜色向量,颜色由红、绿、蓝、alpha这四个通道表示,和CSS里的表示一样,这里把当前像素的红色通道设置为归一化的x坐标,其他通道都写死。
 
3、Javascript扮演的角色
 
webGL通过执行GESL ES代码编写的着色器来渲染图形,那么Javascript是用来干什么的呢?Javascript起到调度作用,它通过调用webGL接口来编译着色器、给着色器传参、控制渲染的执行时机等等。下面的Javascript代码使用了twgl.js来调用webGL接口来编译着色器,给顶点着色器传递顶点数据(arrays),给片段着色器传递canvas元素的宽高(iResolution),并且在requestAnimationFrame函数回调中执行渲染。
 
import {
	resizeCanvasToDisplaySize,
	createProgramInfo,
	createBufferInfoFromArrays,
	setBuffersAndAttributes,
	setUniforms,
	drawBufferInfo,
} from 'twgl.js';

import glslify from 'glslify';

import vShaderSource from '../../shaders/common.vs';
import fShaderSource from './index.fs';

const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');

const programInfo = createProgramInfo(gl, [glslify(vShaderSource), glslify(fShaderSource)]);

/**
 * a --- b
 * |  \  |
 * c --- d
 */
const arrays = {
	position: {
		numComponents: 3,
		data: [
			-1, -1, 0, // c
			1, -1, 0,  // d
			-1, 1, 0,  // a

			-1, 1, 0,  // a
			1, -1, 0,  // d
			1, 1, 0    // b
		],
	},
};
const bufferInfo = createBufferInfoFromArrays(gl, arrays);

function render(time) {
	resizeCanvasToDisplaySize(canvas);
	gl.viewport(0, 0, canvas.width, canvas.height);
	gl.useProgram(programInfo.program);

	setBuffersAndAttributes(gl, programInfo, bufferInfo);
	
	const uniforms = {
		iResolution: [
			canvas.width,
			canvas.height,
		],
	};
	setUniforms(programInfo, uniforms);
	
	drawBufferInfo(gl, bufferInfo);
}
function renderLoop(time) {
	render(time);
	requestAnimationFrame(renderLoop);
}
requestAnimationFrame(renderLoop);

最终渲染出了如下图像:

渲染的核心还是在片段着色器,所以其他的代码不用看,直接看片段着色器就能很好理解为什么会渲染成上图的样子。因为本文例子中canvas的每个像素都被光栅化了,所以为了简便起见,下面的例子都不用管Javascript代码和顶点着色器代码,直接看片段着色器代码即可看懂渲染的原理。
 
四、绘制图像及产生动画
 
1、绘制矩形
 
首先来认识step函数,它是GLSL内置的一个函数,第一个参数是阈值,第二个参数的值如果小于阈值就会返回0,否则返回1,我们可以利用它来绘制矩形:
 
uniform vec2 iResolution;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;
	float l = step(0.2, st.x);
	float r = step(0.2, 1.0 - st.x);
	float b = step(0.2, st.y);
	float t = step(0.2, 1.0 - st.y);
	float pct = l * r * b * t;
	gl_FragColor = vec4(vec3(pct), 1.0);
}
它会渲染成下面的图像:
 
 
再认识一个内置函数smoothstep,和step函数不同的地方在于,它由两个阈值,阈值之间是一个线性过渡效果,比如把step函数换成smoothstep。
 
float l = smoothstep(0.15, 0.25, st.x);
float r = smoothstep(0.15, 0.25, 1.0 - st.x);
float b = smoothstep(0.15, 0.25, st.y);
float t = smoothstep(0.15, 0.25, 1.0 - st.y);
float pct = l * r * b * t;
会渲染成下面的图像:
 
 
接下来我们将渲染矩形的代码提取为一个函数放到另外一个模块里:
 
/**
* @desc 画矩形模块
* @param w {float} 宽
* @param h {float} 高
* @param x {float} x轴偏移量
* @param y {float} y轴偏移量
* @param st {vec2} 画布坐标
* @param smoothThreshold {float} 羽化程度
* @return {float} 颜色分量0.0到1.0之间
*/
float rect(float w, float h, float x, float y, vec2 st, float smoothThreshold) {
	vec2 bl, tr,
		blDistance = vec2(x, y),
		trDistance = vec2(1.0 - x - w, 1.0 - y - h);
	if (smoothThreshold > 0.0) {
		bl = smoothstep(
			blDistance - vec2(smoothThreshold),
			blDistance + vec2(smoothThreshold),
			st
		);
		tr = smoothstep(
			trDistance - vec2(smoothThreshold),
			trDistance + vec2(smoothThreshold),
			1.0 - st
		);
	} else {
		bl = step(blDistance, st);
		tr = step(trDistance, 1.0 - st);
	}
	return bl.x * bl.y * tr.x * tr.y;
}

#pragma glslify: export(rect)
 
这样我们可以导入该模块来绘制矩形:
 
#pragma glslify: rect = require(../../shaders/rect.glsl);
uniform vec2 iResolution;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;
	float pct = rect(0.5, 0.5, 0.25, 0.25, st, 0.0);
	gl_FragColor = vec4(vec3(pct), 1.0);
}

2、加入动画

前面Javascript代码中我们将时间戳参数传给了着色器,可以利用它实现动画:

#pragma glslify: rect = require(../../shaders/rect.glsl);

uniform vec2 iResolution;
uniform float iTime;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;
	float pct = rect(0.5, 0.5, abs(sin(iTime)) * 0.5, 0.25, st, 0.0);
	gl_FragColor = vec4(vec3(pct), 1.0);
}

 

五、贴图,模拟photoshop图层混合效果
 
我们知道片元着色器能够控制光栅化后的每个像素的颜色,意味着我们可以绘制出任意复杂的图像,比如画一副风景图,但这是吃力不讨好的行为,有更好的方法来做,就是使用纹理贴图。我们把一幅准备好的风景图作为纹理贴到webGL绘制的图形上就可以了。纹理贴图的原理很简单:向片元着色器提供纹理图像(采样器)和纹理坐标,然后在片元着色器的逐像素操作中,将纹理坐标映射的纹理图像的像素颜色(纹素)提取出来,赋值给光栅化后的像素。
 
片段着色器很简单,传入了纹理图像(iChannel0)和纹理坐标(iResolution),并在入口函数里完成了纹理采样的过程。texture2D函数是GLSL ES内置函数,用于2D纹理的采样。
 
uniform vec2 iResolution;
uniform sampler2D iChannel0;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;
	gl_FragColor = texture2D(iChannel0, st);
}

上面的例子只是贴一张纹理图,我们也可以贴多张纹理图,并通过一定的算法混合多幅图像,得到特殊的效果。比如photoshop里面图层的混合方式,正片叠底、滤色、线性加深(减淡)等等,都可以实现。其实这很简单,我们把片段着色器改改:

uniform vec2 iResolution;
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;
	vec4 texture0 = texture2D(iChannel0, st);
	vec4 texture1 = texture2D(iChannel1, st);
	// 正片叠底
	vec4 color = texture0 * texture1;
	// 滤色
	// vec4 color = 1.0 - (1.0 - texture0) * (1.0 - texture1);
	// 线性减淡
	// vec4 color = texture0 + texture1;
	// 线性加深
	// vec4 color = texture0 + texture1 - 1.0;

	gl_FragColor = color;
}

六、模拟自然效果-噪声
 
片段着色器有强大的造型能力,可以模拟自然界中的一些效果,比如火焰、金属、木头纹理等等。下面的片段着色器使用第三方噪声模块产生木头纹理的效果:
 
#pragma glslify: snoise2 = require(glsl-noise/simplex/2d)

// 圆周率
#define PI 3.14159265359

/**
* @desc 2d旋转函数
* @param {float} angle 旋转弧度
* @return {mat2} 旋转矩阵
*/
mat2 rotate2d(float angle){
	return mat2(
		cos(angle), -sin(angle),
		sin(angle), cos(angle)
	);
}

/**
* @desc 画横线
* @param {vec2} pos 坐标
* @param {float} b 线的宽度
* @return {float} 0.0到1.0的权重值
*/
float lines(in vec2 pos, float b){
	float scale = 10.0;
	pos *= scale;
	return smoothstep(
		0.0,
		0.5 + b * 0.5,
		abs((sin(pos.y * PI) + b * 2.0)) * 0.5
	);
}

/**
* @desc 画木头纹理
* @param {vec2} st 坐标
* @return {float} 0.0到1.0的权重值
*/
float drawWoodTexture(in vec2 st, in float time) {
	vec2 pos = st * vec2(2.0, 2.0);
	// pos = rotate2d(snoise2(pos)) * pos;
	pos = rotate2d(snoise2(pos) * abs(sin(time / 10.0))) * pos;
	float pct = lines(pos, 0.2);
	return pct;
}

uniform vec2 iResolution;
uniform float iTime;

void main() {
	vec2 st = gl_FragCoord.xy / iResolution;

	float pct = drawWoodTexture(st, iTime);
	vec3 color = vec3(pct);

	gl_FragColor = vec4(color, 1.0);
}

 

七、使用第三方模块
 
除了自己去实现一些片段着色器,也可以导入第三方模块来使用。下面的例子导入第三方的rain模块来实现涟漪的效果。
 
precision highp float;

#pragma glslify: testRain = require(./rain.glsl)

uniform vec2 iResolution;
uniform float iTime;
uniform vec2 iMouse;
uniform sampler2D iChannel0;

void main() {
	testRain(gl_FragColor, gl_FragCoord.xy, iResolution, iTime, iMouse, iChannel0);
}

 

文章来自个人专栏
文章 | 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0