从零开始手撸WebGL3D引擎5:Shader的封装
上一篇之后停了好久没写,但其实mini3d.js一直在进展,目前的内容够写很多篇了,但这样欠下的债就太多了。我开始思考我写这些文章的初衷,是对这个过程的记录以及思考的总结,而不是写WebGL教程。教程的问题是如果讲得很细非常花篇章,而讲得很简略又没有用处。所以本篇开始减少代码细节的描述,只重点讨论技术原理和设计思路。并且每一组文章都要紧扣着当前讨论的里程碑目标。
本篇对应代码请参考:mini3d.js
回顾
上一篇中,我们讨论了mini3d.js对静态模型的封装,提供了自定义顶点格式的支持,以及顶点属性和shader中attribute的绑定。模型为我们当前的目标-渲染一个立方体提供了数据,但是具体怎么渲染它需要shader的支持。那么什么是shader,shader具体做了啥,mini3d.js目前对shader提供了哪些封装和接口,是本篇要讨论的问题。
材质和Shader
WebGL的shader狭义上说是指一组运行在GPU上的小程序,vertex shader和fragment shader。其中VS的作用是输出裁剪空间的顶点和其他中间变量,FS的作用是计算片段的颜色。
但是光靠VS和FS往往还不够,WebGL还可以设置各种渲染状态,如混合的参数,并且某些渲染方式可能需要执行多次不同的渲染,称为多个PASS,并且某些引擎会提供VS/FS无法执行时的替代解决方案(fallback)。所有的这些综合起来可以在一个文件里面定义(或者通过代码组织),一些引擎叫做材质(Material),而另一些引擎可能就叫shader(例如unity)。材质只是一种渲染方案的模板,可能有多个物体使用同一个材质渲染,但是有不同的参数值(如diffuse贴图),这些具有不同参数值的材质在某些引擎里面叫做材质实例(Material Instance)。而unity中的材质其实是unity shader的实例,相当于前述引擎的Material Instance。虽然叫法不同,但是思路都很类似。mini3d.js也会实现材质系统,但是目前先把shader封装一下,因为shader本身也有一些细节。
WebGL shader的输入输出
WebGL的shader是GLSL ES语言的小程序,和OpenGL ES 2.x/3.x基本是通用的。我们看一下OpenGL ES 3.0的VS和FS的输入输出:
其中VS输入Attribute和Uniform (先忽略Sampler),输出Varying和gl_Position以及gl_PointSize
FS输入Varying, Uniform和Sampler,输出gl_FragColor (暂时没找到OpenGL ES 2.0的图,3.0中FS输出了一组Color先不用管它)
对于VS,输入的Attribute就来自我们提交的顶点数据,这个数据的准备工作已经在上一篇中做好了,并且已经和shader进行了绑定。这样执行VS时就可以对于模型的每个顶点执行一次VS,并且传入每个顶点的各个属性。但是为了执行这个绑定,shader必须提供Attribute的Location,所以这方面需要封装一下。 而Uniform输入需要调用相应的API,这也是需要封装的一个点,因为Uniform数据类型很多直接使用API调用不方便。至于Sampler其实也是uniform的一种。
Shader的封装
mini3d.js目前提供了Shader类,这个Shader其实对应了WebGL的一个shader program。目前提供了几个操作。
从shader源码创建shader program
create(vshader, fshader)方法输入参数为vs和fs的源码,目前的demo里面源码是直接写在js代码里面的字符串,下一个里程碑将从资源文件载入。create内部创建了一组shader对象并编译链接为一个program对象。这个是最基本的封装,避免了每次创建shader的繁琐操作。
获取Attribute的location
WebGL中使用gl.getAttribLocation(program, name)
可以通过名字获取location。但是我们不是直接这么做,mini3d.js首先使用gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES)
和gl.getActiveAttrib(program,index)
获取所有的active的attributes的信息,这个信息是一个WebGLActiveInfo
对象,包含name,size,type。对于attribute我们只使用name,然后通过name获取到location并存储在this._attributes = {}; // {[name:string]:number}
中。这样做的好处是防止代码中写错name并且确保attribute是激活的。但由于我们需要将Mesh中的顶点属性和shader的attribute进行绑定,还是需要在代码中输入name,但是毕竟这儿做了个检查。另外这么做相当于空间换时间,将所有的attribute location缓存起来避免重复查询。以上操作封装在findoutAttributes
方法中并在create中自动调用。使用者只需要使用getAttributeLocation(semantic)
接口通过semantic获取location。注意参数为semantic,这个就是在Mesh中定义的顶点属性语义,通过这个semantic将顶点属性和shader的attribute联系起来。
关联semantic和Attribute name
上面说了我们使用semantic获取attribute location,因此我们需要知道semantic对应的attribute name。Shader类提供了接口mapAttributeSemantic(semantic, attribName)
进行设置。这么做还是需要在代码中写shader中的名字,但是这给以后的升级提供了基础,比如像Unity那样使用CG就可以直接将semantic标记在shader的变量后面,这样就可以自动化了。当然mini3d.js大概率不会使用CG,毕竟那需要工具链的支持,但我有可能在材质定义中进行标记,这样代码中就不用出现name了,而标记和shader都包裹在材质文件中,避免信息分散维护。
设置Uniform
WebGL关于设置uniform的API有gl.uniform[1234][fi][v]()
文档和.uniformMatrix[234]fv()
文档
太多了吧,调用起来太麻烦,作为一个框架必须得管理这个复杂度。好在我们是javascript,没有问题,我们封装了一个方法Shader类的setUniform(name, value)
。不管是什么类型的uniform,都用这个方法设置。例如:
//设置矩阵
shader.setUniform('u_mvpMatrix', mvpMatrix.elements);
//设置sampler
shader.setUniform('u_sampler', 0);
//设置vec3
shader.setUniform('u_LightColor', [1.0,1.0,1.0]);
后面两个暂时超纲了,里程碑1还没有。
这个setUniform做了所有的苦活累活,基本原理就是先获取shader中所有active的uniform的信息(在findoutUniforms
中获取)。这个信息是一个如下的对象:
class UniformInfo{ constructor(name, location, type, size, isArray){ this.name = name; this.location = location; //WebGLUniformLocation this.type = type; this.size = size; this.isArray = isArray; }
}
然后在setUniform
方法中会根据name所对应的info的类型调用不同的API进行设置。目前阶段还没支持所有的类型组合,先支持了一些常用的。
小结和展望
目前这个阶段对shader进行了简单的封装,降低调用的复杂度并且可以更方便的和模型进行绑定。mini3d.js没有像某些引擎那样写死所有常用的顶点属性然后定死了attribute的名字,而是通过semantic进行关联,这主要是为了扩展性,必须对于一个基于shader的渲染框架来说,让用户可以自由的写shader非常重要。虽然想达到Unity那样的成熟度很困难,但我们可以一步一步来。未来肯定会写材质系统,甚至shader graph这样方便技美创作的工具,但是到写工具的那一步就真的向一个引擎发展了,目前来说先实现基本目标吧,毕竟太多的事情想做。比如说提供一些常用的uniform,如各种矩阵,按照某个约定的名字在渲染前自动传入到shader中,再如提供一些公共方法方便写shader时调用,甚至像对于阴影的支持这种,都需要引擎提供可在shader中获取的数据。
从零开始手撸WebGL3D引擎5:Shader的封装相关推荐
- 从零开始手撸一个热修复框架
1.前言 热修复原理,这个一直是这几年来很热门的话题,在项目中使用的话,也基本要么是阿里系或者腾讯系的开源框架.但是作为一个光会使用的程序员是远远不够的.这篇文章会从dex分包的原因,原理,热修复的由 ...
- umi脚手架搭建的项目_还在从零开始搭建项目?手撸了款快速开发脚手架!
之前开源了一款项目骨架mall-tiny,完整继承了mall项目的整个技术栈.总感觉mall-tiny集成了太多中间件,过于复杂了.这次对其进行了简化和升级,使它成为了一款拥有完整权限管理功能的快速开 ...
- 1.从零开始手敲次时代游戏引擎(序言)
大家好.我"正式"从事软件工程师这个职业已经快15年了.至于编程的历史则更长,有20余年了.记忆当中第一次编程的机器里只有ROM BASIC,用"*"打了个金字 ...
- 从零开始手敲次时代游戏引擎(目录)
原文链接:https://zhuanlan.zhihu.com/c_119702958 目录 1.从零开始手敲次世代游戏引擎(序) 2.从零开始手敲次世代游戏引擎(HelloEngine) 3.从零开 ...
- .NET手撸2048小游戏
前言 2048是一款益智小游戏,得益于其规则简单,又和 2的倍数有关,因此广为人知,特别是广受程序员的喜爱. 本文将再次使用我自制的"准游戏引擎" FlysEngine,从空白窗口 ...
- 手撸一款精美的水波气泡
代码地址如下: http://www.demodashi.com/demo/13434.html Android自定义水波气泡 前言:公司在做的一个项目,要求在地图上以水波气泡的形式来显示站点,并且气 ...
- Unity URP 手撸一个自己的PBR材质
嘿嘿,你能认出哪个是官方的lit shader,哪个是我手撸的PBRshader吗 . 然后就可以魔改成风格化的PBR拉 先占个坑,后面有空的话会梳理一遍URP的pbr流程和相关公式 最后一定要注意自 ...
- 耗时两周手撸了一个 RPC 轮子,是驴子是马拉出来遛遛
手撸 RPC 轮子系列文章目录: 「从零开始造 RPC 轮子系列」01 我为什么要去造一个轮子? 「从零开始造 RPC 轮子系列」02 演示轮子,是驴是马拉出来遛遛] (TODO)「从零开始造 RPC ...
- 手撸架构,Kafka 面试42问
技术栈 传送门 JAVA 基础 手撸架构,Java基础面试100问_vincent-CSDN博客 JAVA 集合 手撸架构,JAVA集合面试60问_vincent-CSDN博客 JVM 虚拟机 手撸架 ...
- 手撸架构,Redis面试41问
技术栈 传送门 JAVA 基础 手撸架构,Java基础面试100问_vincent-CSDN博客 JAVA 集合 手撸架构,JAVA集合面试60问_vincent-CSDN博客 JVM 虚拟机 手撸架 ...
最新文章
- 利用BP神经网络教计算机识别语音特征信号(代码部分SS)
- 安卓中实现两端对齐,中间fill_parent的方法
- BZOJ1083: [SCOI2005]繁忙的都市
- Typecho给文章设置永久链接
- jQuery 对HTML的操作(二)
- java实现遍历树形菜单方法——OpenSessionView实现
- 推荐几个练习听力不错的国外网站
- amd cpu不能在cmd环境下运行java代码_如何在Windows10中配置java的JDK环境
- 弹性计算平台技术:云服务器“安全”“稳定”“弹性”的基石
- Python学习笔记(5):Python如何忽略warning的输出
- 图片人脸检测——OpenCV版(二)
- linux系统ftp优化,Linux vsftp 部署优化
- 巧用负载均衡 解决数据中心三大困惑
- (33)FPGA面试题附加约束的作用
- nginx限流方案的实现(三种方式)
- 热门NPM库 “coa” 和“rc” 接连遭劫持,影响全球的 React 管道
- fw313r手机登录_迅捷(FAST)fw313r路由器手机设置教程 | 192路由网
- 关于水晶易表的简介及水晶易表安装初识
- Netlog的数据库及LAMP架构
- DEBUG指示灯详细说明
热门文章
- 机器学习中的数学(下)
- Linux之wubi输入法
- Hadoop生态系统概述
- Cadence Allegro 导出Netin(back anno.)报告详解
- cmd无敌加密安全代码
- 企业网站初创,如何做好网络营销优化?
- LeetCode Top 100 Liked Questions 226. Maximal Square (Java版; Medium)
- 云原生 envoy xDS 动态配置 java控制平面开发 支持restful grpc实现 EDS 动态endpoint配置
- 聊聊区块链--如何投资数字货币
- 南开大学计算机控制工程学院院长,南开大学计算机与控制工程学院研究生导师简介-任博...