之前群里有粉丝提问, 就是shader不是很理解。然后今天他就来了, 废话不多说,读完今天的这篇文章你可以学到以下几点:
2 . shader 中的每个参数到底是什么意思??怎么去用???
你如果会了,这篇文章你可以不用看,不用浪费时间,去看别的文章。如果哪里写的有问题欢迎大家指正,我也在不断地学习当中。
这里我结合自己的思考,讲讲webgl的整个的一个渲染过程。
「Webgl」的渲染依赖底层「GPU」的渲染能力。所以「WEBGL」 渲染流程和 「GPU」 内部的渲染管线是相符的。
「渲染管线的作用是将3D模型转换为2维图像。」
在早期,渲染管线是不可编程的,叫做「固定渲染管线」,工作的细节流程已经固定,修改的话需要调整一些参数。
现代的 「GPU」 所包含的渲染管线为「可编程渲染管线」,可以通过编程 「GLSL 着色器语言」 来控制一些渲染阶段的细节。
简单来说:就是使用「shader」,我们可以对画布中「每个像素点做处理」,然后就可以生成各种酷炫的效果了。
渲染过程大概经历了下面这么多过程, 因为本篇文章的重点其实是在着色器,所以我重点分析从「顶点着色器」—— 「片元着色器」的一个过程
WebGL就是和GPU打交道,在GPU上运行的代码是一对着色器,一个是顶点着色器,另一个是片元着色器。每次调用着色程序都会先执行顶点着色器,再执行片元着色器。
一个顶点着色器的工作是生成裁剪空间坐标值,通常是以下的形式:
const vertexShaderSource = `
attribute vec3 position;
void main() {
gl_Position = vec4(position,1);
}
`
每个顶点调用一次(顶点)着色器,每次调用都需要设置一个特殊的全局变量 「gl_Position」。该变量的值就是裁减空间坐标值。这里有同学就问了, 什么是「裁剪空间的坐标值」???
其实我之前有讲过,我在讲一遍。
何为裁剪空间坐标?就是无论你的画布有多大,裁剪坐标的坐标范围永远是 -1 到 1 。
看下面这张图:
裁剪坐标系
如果运行一次顶点着色器, 那么gl_Position 就是**(-0.5,-0.5,0,1)** 记住他永远是个 「Vec4」, 简单理解就是对应「x、y、z、w」。即使你没用其他的,也要设置默认值, 这就是所谓的 3维模型转换到我们屏幕中。
顶点着色器需要的数据,可以通过以下四种方式获得。
属性可以用 float
, vec2
, vec3
, vec4
, mat2
, mat3
和 mat4
数据类型
所以它内建的数据类型例如vec2
, vec3
和 vec4
分别代表两个值,三个值和四个值, 类似的还有mat2
, mat3
和 mat4
分别代表 2x2, 3x3 和 4x4 矩阵。你可以做一些运算例如常量和矢量的乘法。看几个例子吧:
vec4 a = vec4(1, 2, 3, 4);
vec4 b = a * 2.0;
// b 现在是 vec4(2, 4, 6, 8);
向量乘法 和矩阵乘法 :
mat4 a = ???
mat4 b = ???
mat4 c = a * b;
vec4 v = ???
vec4 y = c * v;
它还支持矢量「调制」,意味者你可以交换或重复分量。
v.yyyy === vec4(y, y, y,y )
v.bgra === vec4(v.b,v.g,v.r,v.a)
vec4(v.rgb, 1) === vec4(v.r, v.g, v.b, 1)
vec4(1) === vec4(1, 1, 1, 1)
这样你在处理图片的时候可以轻松进「行 颜色通道 对调」, 发现你可以实现各种各样的滤镜了。
后面的属性在下面实战中会讲解:我们接着往下走:
「什么是图元?」
❝「描述各种图形元素的函数叫做图元,描述几何元素的称为几何图元(点,线段或多边形)。点和线是最简单的几何图元」经过顶点着色器计算之后的坐标会被组装成「组合图元」。
❞
「通俗解释」:「图元就是一个点、一条线段、或者是一个多边形。」
「什么是图元装配呢?」
「简单理解就是说将我们设置的顶点、颜色、纹理等内容组装称为一个可渲染的多边形的过程。」
组装的类型取决于:你最后绘制选择的图形类型
gl.drawArrays(gl.TRIANGLES, 0, 3)
「如果是三角形的话,顶点着色器就执行三次」
「什么是光栅化:」
通过图元装配生成的多边形,计算像素并填充,「剔除」不可见的部分,「剪裁」掉不在可视范围内的部分。最终生成可见的带有颜色数据的图形并绘制。
「光栅化流程图解:」
光珊化图解
「剔除」:
在日常生活中,对于不透明物体,背面对于观察者来说是不可见的。同样,在「webgl」中,我们也可以设定物体的背面不可见,那么在渲染过程中,就会将不可见的部分剔除,不参与绘制。节省渲染开销。
「剪裁」:
日常生活中不论是在看电视还是观察物体,都会有一个可视范围,在可视范围之外的事物我们是看不到的。类似的,图形生成后,有的部分可能位于可视范围之外,这一部分会被剪裁掉,不参与绘制。以此来提高性能。这个就是「视椎体」, 在范围内能看到的东西,才进行绘制。
「光珊化后,每一个像素点都包含了 颜色 、深度 、纹理数据, 这个我们叫做片元」
❝小tips :每个像素的颜色由片元着色器的「gl_FragColor」提供
❞
接收光栅化阶段生成的片元,在光栅化阶段中,已经计算出每个片元的颜色信息,这一阶段会将片元做逐片元挑选的操作,处理过的片元会继续向后面的阶段传递。「片元着色器运行的次数由图形有多少个片元决定的」。
「逐片元挑选」
通过模板测试和深度测试来确定片元是否要显示,测试过程中会丢弃掉部分无用的片元内容,然后生成可绘制的二维图像绘制并显示。
在进行实战之前,我们先给你看一张图,让你能大概了解,用原生webgl生成一个三角形需要那些步骤:
draw
我们就跟着这个流程图一步一步去操作:
新建一个webgl画布
<canvas id="webgl" width="500" height="500"></canvas>
创建webgl 上下文:
const gl = document.getElementById('webgl').getContext('webgl')
着色器的程序这些代码,其实是重复的,我们还是先看下图,看下我们到底需要哪些步骤:
shader
那我们就跟着这个流程图:一步一步来好吧。
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
gl.VERTEX_SHADER 和 gl.FRAGMENT_SHADER 这两个是全局变量 分别表示「顶点着色器」 和「片元着色器」
顾名思义:数据源,也就是我们的着色器 代码。
编写着色器代码有很多种方式:
我们先写顶点着色器:
const vertexShaderSource = `
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
`
顶点着色器 必须要有 main 函数 ,他是强类型语言, 「记得加分号哇」 不是js 兄弟们。我这段着色器代码非常简单 定义一个vec4 的顶点位置, 然后传给 gl_Position
这里有小伙伴会问 ?这里「a_position」一定要这么搞??
这里其实是这样的哇, 就是我们一般进行变量命名的时候 都会增加带有关键词的前缀 用来区分每个变量的名字 他是属性 还是 全局变量 还是纹理 比如这样:
uniform mat4 u_mat;
表示个矩阵,如果不这样也可以哈。但是要专业呗,防止bug 影响。
我们接着写片元着色器:
const fragmentShaderSource = `
void main() {
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
`
这个其实理解起来非常简单哈, 每个像素点的颜色 是红色 , gl_FragColor 其实对应的是 「rgba」 也就是颜色的表示。
有了数据源之后开始绑定:
// 创建着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
//绑定数据源
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
是不是很简单哈哈哈哈,我觉得你应该会了。
其实后面「编译着色器」、「绑定着色器」、「连接着色器程序」、「使用着色器程序」 都是一个api 搞定的事不多说了 直接看代码:
// 编译着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建着色器程序
const program = gl.createProgram()
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接 并使用着色器
gl.linkProgram(program)
gl.useProgram(program)
这样我们就创建好了一个着色器程序了。
这里又有人问,我怎么知道我创建的着色器是对的还是错的呢?我就是很粗心的人呢???好的他来了 如何调试:
const success = gl.getProgramParameter(program, gl.LINK_STATUS)
if (success) {
gl.useProgram(program)
return program
}
console.error(gl.getProgramInfoLog(program), 'test---')
gl.deleteProgram(program)
「getProgramParameter」 这个方法用来判断 我们着色器 「glsl」 语言写的是不是对的, 然后你可以通过 「getProgramInfoLog」这个方法 类似于打 日志 去发现❌了。
有了着色器,现在我们差的就是数据了对吧。
上文在写顶点着色器的时候用到了Attributes属性,说明是「这个变量要从缓冲中读取数据」,下面我们就来把数据存入缓冲中。
首先创建一个顶点缓冲区对象(Vertex Buffer Object, VBO)
const buffer = gl.createBuffer()
gl.createBuffer()函数创建缓冲区并返回一个标识符,接下来需要为WebGL绑定这个buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bindBuffer()函数把标识符buffer设置为「当前缓冲区」,后面的所有的数据都会都会被放入当前缓冲区,「直到bindBuffer绑定另一个当前缓冲区」。
我们新建一个数组 然后并把数据存入到缓冲区中。
const data = new Float32Array([0.0, 0.0, -0.3, -0.3, 0.3, -0.3])
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
因为「JavaScript与WebGL通信必须是二进制的」,不能是传统的文本格式,所以这里使用了ArrayBuffer对象将数据转化为二进制,因为顶点数据是浮点数,精度不需要太高,所以使用Float32Array就可以了,这是JavaScript与GPU之间大量实时交换数据的有效方法。
「gl.STATIC_DRAW」 指定数据存储区的使用方法:缓存区的内容可能会经常使用,但是不会更改
「gl.DYNAMIC_DRAW」 表示 缓存区的内容经常使用,也会经常更改。
「gl.STREAM_DRAW」 表示缓冲区的内容可能不会经常使用
「GLSL」着色程序的唯一输入是一个属性值「a_position」。我们要做的第一件事就是从刚才创建的GLSL着色程序中找到这个属性值所在的位置。
const aposlocation = gl.getAttribLocation(program, 'a_position')
接下来我们需要告诉「WebGL」怎么从我们之前准备的缓冲中获取数据给着色器中的属性。首先我们需要启用对应属性
gl.enableVertexAttribArray(aposlocation)
最后是从缓冲中读取数据绑定给被激活的「aposlocation」的位置
gl.vertexAttribPointer(aposlocation, 2, gl.FLOAT, false, 0, 0)
gl.vertexAttribPointer()函数有六个参数:
现在着色器程序 和数据都已经ready 了, 现在就差渲染了。渲染之前和2d canvas 一样做一个清除画布的动作:
// 清除canvas
gl.clearColor(0, 0, 0, 0)
gl.clear(gl.COLOR_BUFFER_BIT)
我们用「0、0、0、0」清空画布,分别对应 「r, g, b, alpha (红,绿,蓝,阿尔法」)值, 所以在这个例子中我们让画布变透明了。
开启绘制三角形:
gl.drawArrays(gl.TRIANGLES, 0, 3)
「绘制类型共有下列几种」 「看图:」
drawtype
这里我们看下画面是不是一个红色的三角形 :
三角形截图
我们创建的数据是这样的:
「画布的宽度是 500 * 500 转换出来的实际数据其实是这样的」
0,0 ====> 0,0
-0.3, -0.3 ====> 175, 325
0.3, -0.3 ====> 325, 325
有了静态的图形我们开始着色器,对三角形做一个缩放。
改写顶点着色器:其实在顶点着色器上加一个全局变量 这就用到了 着色器的第二个属性 uniform
const vertexShaderSource = `
attribute vec4 a_position;
// 添加矩阵代码
uniform mat4 u_mat;
void main() {
gl_Position = u_mat * a_position;
}
`
然后和属性一样,我们需要找到 uniform 对应的位置:
const matlocation = gl.getUniformLocation(program, 'u_mat')
然后初始化一个缩放举证:
// 初始化一个旋转矩阵。
const mat = new Float32Array([
Tx, 0.0, 0.0, 0.0,
0.0, Ty, 0.0, 0.0,
0.0, 0.0, Tz, 0.0,
0.0, 0.0, 0.0, 1.0,
]);
Tx, Ty, Tz 对应的其实就是 x y z 轴缩放的比例。
最后一步, 将矩阵应用到着色器上, 在画之前, 这样每个点 就可以✖️ 这个缩放矩阵了 ,所以整体图形 也就进行了缩放。
gl.uniformMatrix4fv(matlocation, false, mat)
三个参数分别代表什么意思:
OK 我写了三角形缩放的动画:
let Tx = 0.1 //x坐标的位置
let Ty = 0.1 //y坐标的位置
let Tz = 1.0 //z坐标的位置
let Tw = 1.0 //差值
let isOver = true
let step = 0.08
function run() {
if (Tx >= 3) {
isOver = false
}
if (Tx <= 0) {
isOver = true
}
if (isOver) {
Tx += step
Ty += step
} else {
Tx -= step
Ty -= step
}
const mat = new Float32Array([
Tx, 0.0, 0.0, 0.0,
0.0, Ty, 0.0, 0.0,
0.0, 0.0, Tz, 0.0,
0.0, 0.0, 0.0, 1.0,
]);
gl.uniformMatrix4fv(matlocation, false, mat)
gl.drawArrays(gl.TRIANGLES, 0, 3)
// 使用此方法实现一个动画
requestAnimationFrame(run)
}
效果图如下:
缩放动画
最后 给大家看一下webgl 内部是怎么搞的 一张gif 动画 :
vertex-shader-anim
原始的数据通过 顶点着色器 生成一系列 新的点。
说完矩阵了下面,我们开始说下着色器中的varying 这个变量 是如何和片元着色器进行联动的。
我们还是继续改造顶点着色器:
const vertexShaderSource = `
attribute vec4 a_position;
uniform mat4 u_mat;
// 变量
varying vec4 v_color;
void main() {
gl_Position = u_mat * a_position;
v_color = gl_Position * 0.5 + 0.5;
}
`
这里有一个小知识 , gl_Position 他的值范围是 「-1 -1」 但是片元着色 他是颜色 他的范围是 「0 - 1」 , 所以呢这时候呢,我们就要 做一个范围转换 所以为什么要 乘 0.5 在加上 0.5 了, 希望你们明白。
改造下片元着色器:
const fragmentShaderSource = `
precision lowp float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
`
只要没一个像素点 改为由顶点着色器传过来的就好了。
我们看下这时候的三角形 变成啥样子了。
彩色三角形
是不是变成彩色三角形了, 这里很多人就会问, 这到底是怎么形成呢, 本质是在三角形的三个顶点, 做线性插值的过程:
本篇文章大概是对webgl 做了一个基本的介绍, 和带你用几个简单的小例子 带你入门了glsl 语言, 你以为webgl 就这样嘛 那你就错了,其实有一个texture 我是没有讲的, 后面我去专门写一篇文章去将纹理贴图 , 漫反射贴图、 法线贴图。 我们下期再见
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/eOFE-W0FkWt6vTI2c9mRQA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。