最近有幸总结了一下kanzi中opengl的东西,实际比较重点的也就是shader部分,对于其他部分,这里也会简单的说,但是具体的功能作用就要自己下来慢慢研究了
一:引言
本文章主要介绍kanzi和opengl之间的一些问题,可能会比较杂,因为很多东西也无法预测他们具体是如何结合,故我们会以kanzi为主,在kanzi的基础上介绍opengl的一些东西。
通过kanzi help doc中的opengl接口,我们可以了解kanzi也是支持modern opengl,故老的opengl接口是不支持的。具体接口可见<system/wrappers/kzs_opengl.h>
这里的东西比较杂,我们不会具体的说,这里会分几个大块来针对性的说明。
二:具体介绍
一:着色器
一:shader(着色器)
着色器(Shader)是用来实现图像渲染的,用来替代固定渲染管线的可编辑程序。其中Vertex Shader主要负责顶点的几何关系等的运算,Pixel Shader(Fragment Shader)主要负责片源颜色等的计算。
着色器替代了传统的固定渲染管线,可以实现3D图形学计算中的相关计算,由于其可编辑性,可以实现各种各样的图像效果而不用受显卡的固定渲染管线限制。
目前比较流行的Shader语言除了GLSL之外还有HLSL,RM等,当然,我们目前主要使用GLSL。
Opengl的绘图管线
当然,这部分opengl的部分这里只是简单介绍。我们只需知道整个工作流程即可。
- 准备顶点数据:
在CPU中进行。这里会定义一些VAO和VBO,他们包含了每个一些顶点数据的信息,在shader中的体现即attribute。
在kanzi中,我们定义的EmptyNode2D或者Image2D...在这部分的体现即会准备不重复的4个点即重复的6个点,构成2个三角形。然后会将这些点绑定到VAO或者VBO中,发送给GPU。
- Vertex Processing:
调用Vertex Shader处理顶点数据信息。这个阶段会接受传递过来的attribute数据作为输入,然后通过一些转换后输出。每个顶点都是1对1。
Kanzi中我们可以看到attribute属性的有kzPosition,kzTextureCoordinate0及其他。
- Primitive Assembly
图元装配:即把vertex shader输出的顶点数据集合到一起,并把它组合成一个图元的过程。这个输出的类型由用户决定,一般输出的是三角形,这个取决于你drawcall的类型。
- Geometry Shading: 这个目前不做介绍,它即对Primitive Assembly输出的图元处理后再输出图元。
- Rasterization
光栅化:一系列处理后的图元会被光栅化为一系列的‘候选像素点’。
- Fragment Processing
调用Fragment shader处理像素点。处理后我们只需要得到out的像素即gl_FragColor。
- Pre-Fragment :
- 生成frame buffer后GPU显示出来。
这部分东西很多,而我们应该了解的东西总结一下:
- Fragment Shader处理像素点,故一些运算如果可以放到Vertex shader中最好。
- 变量的属性:
Attribute:输入的定点属性,无法改变。
Uniform:用户定义的属性,可在外部改变。
Varying:在vertex shader和fragment shader传递变量
- Float精度
highp:
mediump:
lowp:
使用默认精度可加precision mediump float;
- 着色器尽量简单。
二:brush及Material
Kanzi中,如果需要对自带的2D节点添加材质,则需要一个中介brush。
如果我们需要在一个Node2D上画出某个东西(颜色,材质或者图片),我们需要借助一个brush来操作。
伪代码:
- 使用Material Brush:
Material::Create();
Shader::Create()
Shader->InitializeFromMemory()
Material->setShader()
MaterialBrush::Create(Material)
BrushResource = kzuBrushResourceCreateFromBrush(MaterialBrush)
Node->setBackGroundBrush(BrushResorce)
- Color Brush和TextureBrush同理。
故思考:如果我们需要提高开机速度,那么如果这个节点刚好有Material或者其他,那么我们可将这部分的内容拆开放到后面处理。测试,添加material和不加material加载速度确实是有差异的。
材质:通俗的将就是材质决定我们想要的物体呈现什么样子。可以理解为是编译shader后生成的一个实例,材质的属性就是shader uniform开放的变量。所以我们通过材质的属性可以达到我们想要的效果。
在kanzi中,shader这部分我们无法输入attribute,所以在我们操作范围内只能去改变out color。在其中,我们可以添加uniform去控制我们想要达到的效果。
在编写shader过程中,考虑到帧率因素,有以下优化方案:
- 较少甚至禁用 uniform varying的if或者for。
- 由于gpu中的运算时并行运算,所以在有if...else判断中,会根据最慢的执行计算,所以我们需减少if...else执行时间。
- GPU中计算实际为矩阵运算,而float在计算时相当于也会转换为四维矩阵的运算,所以我们应当减少运算的次数。
- 如果是透明或者纯色,我们可直接使用color,减少取纹理。
- 除法运算尽量转为乘法运算。
- 在运算过程中,注意一些复杂的运算符,比如pow,exp,sin,cos,log,tan等。
- Float的精度可根据实际情况使用精度低的类型
- 在计算一些顶点等信息时,能放到vertext中的尽量放到vertextshader中运算。
- 在运算过程中,避免有所依赖。这样并行运算可能会是同步的
扩展:
- Blend Mode:
- Opaque:不开启混融效果
- Automatic:根据Project属性的预设值选择Premultiplied或者Non-Premultiplied,默认是Premultiplied.
- Premultiplied:实际调用glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA)
- Non-Premultiplied:调用glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
- Mixed:调用glBlendSeparateFunc(SRC_ALPHA, ONE_MINUS_SRC_ALPHA, ONE, ONE_MINUS_SRC_ALPHA)
- Additive:调用(GL_ONE, GL_ONE):实际就是两种颜色相加,比如(1,0,0)和(0,1,0)相glBlendFunc加得到(1,1,0)
- BlendIntensity:
- .rgba*BlendIntensity
- .a*BlendIntensity
二:模型
在项目中,很容易看到模型,表盘,车模等等。先说一下模型的构成:
而一起提供过来的还有一张图片,这张图片就是覆盖在模型表面的纹理。而我们看到的图片的形状和模型一般不同,这方面我们不需要关心,模型到底是取得图片的uv到底在哪也不关我们的事,我们只需要做的就是看后期可能会如何处理它的UV。
例:在项目表盘中,我们很容易看到模型,它到底是怎么工作的?
如何导入模型不做介绍,我们可以在kanzi中看到mesh管理器中有对应的mesh,每个mesh的信息也可以看到,三角形数量和顶点的数量、fbx名称、材质等等。
而对于模型的使用和kanzi中3d节点大同小异。
渲染模型流程:
首先,模型除了我们可以操作坐标之外,kanzi中我们是改变不了一个mesh内部的顶点结构的,一个mesh在kanzi中的体现就是model3D。所以在通过材质渲染node时,渲染流程即上面所述流程。顶点以attribute在Vertex shader作为输入,经过转换为世界坐标后输出进行图元装配、光栅化等。Vertex中还会包含uv,这个uv即纹理的坐标(-1, 1)。那在这部分我们是否可以修改这个mesh的内部结构了?
其次,在fragment中,模型已经是固定的,而类似于表盘中的扫光实现原理是什么呢?我们知道在fragment中的运算实际就是计算像素点,将输出的像素表现在模型对应的位置。所以对于表盘中的扫光,我们能看到进度的实际是通过控制纹理的uv坐标让颜色显示出来,而未显示的部分就是一个vec4(0.0)或其他颜色。
最后,对于模型,三角形越多越细致,而导致越占资源;对于纹理,分辨率越大越好,但是影响效率,所以在模型这块就需取一个适当的值。
三:光照
目前项目中用到光照的东西很少,一般像车模可能会加入光照。
首先光照类型:点光,平行光(太阳光),聚光(手电筒)
这些光在kanzi中都有定义,但是具体实际上也是在shader中去模拟光照的一系列计算。
虽说光是一系列的计算,但是opengl对这些光都有固定的说明。我们这里会针对kanzi中的东西做一个说明,其实也和opengl类似。
如何使用光不做介绍,我们说说材质中的光照。
Kanzi给我们提供了很多的光照类型的材质:
Phong:只包含光照
PhoneCube:包含光照和环境纹理
PhongTexture:包含光照和纹理
PhongTextureCube:包含光照、纹理和环境纹理。
对于这几种材质,都有共同属性就是针对光照的:
Ambient Color:环境光颜色
Diffuse Color:漫反射颜色
Emissive Color:反射颜色
Specular Color:镜面反射颜色
Specular Exponent:镜面反射系数
这几种属性可改变光的颜色以及一些光的效果。但是很多我们要实现的效果不仅仅是光可以做完的,我们也许在模型表面需要加入材质,于是我们使用PhongTexture,这个是在属性多了一个Texture属性,我们需要加入环境的颜色比如水面呈现天空的东西,于是我们再加入Cube。对于Cube,我们下节纹理介绍。
四:纹理
这部分其实也没什么东西可说,我们这里说一下压缩纹理和环境纹理。
压缩纹理:目前kanzi支持压缩的纹理类型:ATC、ETC、PVRTC。
很多时候我们用的都是PNG类型生成的纹理,而使用压缩纹理往往是资源可能很大或者影响到性能,但是使用压缩纹理的类型我们需要确定机器是否支持。
支持opengl es2.0(3.0)的是支持ETC1(2)的。我们机器支持的版本是 OpenGL ES 3.0(具体的可通过demo或者kanzi中的opengl接口查看),故我们在使用的时候可压缩成etc格式的压缩纹理,其他格式目前不清楚。
在kanzi中找到resource file->image,在对应图片属性页选中要压缩的目标格式,右键创建纹理即可。
环境纹理:游戏中有天空盒这个东西,但是kanzi中目前是无法使用的,我们可以使用环境纹理。原理:利用环境的颜色/纹理后映射到目的模型上。
环境纹理由六张图片生成,刚好是构成一个立方体。
在kanzi中创建一个环境纹理:Textures->create cube-map texture,然后再属性框添加这 六张图片(注意环境纹理的前后左右上下顺序)。
注:arm支持的最大纹理尺寸:8192
kanzi支持导入的最大纹理尺寸:4096
五:3D
项目中3D场景虽然不多,但是基本每个项目多多少少都包含一些,所以粗略的探讨一下opengl 3d部分在kanzi中的一些东西。
一:深度测试
深度:该像素点距3d中的摄像机的距离(绘制坐标),深度缓存存储着深度值。
在没有深度测试时一般都是按照顺序绘制,这样会把某些物体覆盖,所以深度测试会根据Z值(远近)来正常显示。
opengl默认不会启用深度测试,但是默认开启深度测试。
glEnable(GL_DEPTH_TEST)
绘制半透明物体不适用。
Kanzi中默认属性如上图所示。启用了深度测试,如果没有通过深度缓存,则会丢弃该片段,还需在每个渲染迭代之前清除深度缓存,否则仍然在使用上一次的深度值。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
禁用深度缓冲的写入在kanzi中没找到如何做,这种一般使用一个只读的深度缓冲。
二:Frustum Culling
将不可见的物体提前剔除。
三:Face Cull
面剔除。
剔除背向面,前向面,不启用剔除。
Opengl默认不启用面剔除,而kanzi默认启用背面剔除,意思显而易见就是背面的面会丢弃不渲染。
四:模板测试
片段着色器处理完一个片段之后,经过深度测试丢弃一些片段,被保留的片段进入模板测试,他可能会丢弃更多。深度测试根据深度缓冲进行,而模板测试也是根据模板缓冲进行。
模板缓冲会首先被清除为0,之后再模板缓冲中用1填充,然后在场景中的片段只会在模板值为1时被渲染,剩下的都会被丢弃。
Kanzi默认不启用模板测试,如果启用模板测试,一般有如下步骤:
- 启用模板缓冲的写入
- 渲染物体,更新模板缓冲的内容
- 禁用模板缓冲的写入
- 渲染其他物体,根据模板缓冲的内容丢弃特定的片段。
在每次迭代之前清除模板缓冲。
Stencil Fail Operation: 模板测试失败时的行为
Stencil Pass Depth Fail Operation: 模板测试通过,但深度测试失败时采取的行为
Stencil Pass Depth Pass Operation: 模板测试和深度测试都通过时采取的行为
行为具体有下面
Keep:保持当前存储的模板值
Zero:将模板值设置为0
Replace:将模板值设置为参考值(Stencil Function Reference Value)
Increase:如果模板值小于最大值则将模板值加1
IncreaseWrap:如果模板值超过了最大值则归零
Decrease:如果模板值大于最小值减1
DecreaseWrap:如果模板值小于0则将其设置为最大值
Invert:按位翻转当前的模板缓冲值
opengl和kanzi默认的行为都是keep。
Stencil Function:应用到已存储的模板值上的选项:
Always:永远通过测试
Never:永远不通过测试
Less:在片段模板值小于缓冲的模板值时通过测试
Lequal:在片段模板值小于等于缓冲区的模板值时通过测试
Greater:在片段模板值大于缓冲区的模板值时通过测试
Gequal:在片段模板值大于等于缓冲区的模板值时通过测试
Equal:在片段模板值等于于模板的模板值时通过测试
Notequal:在片段模板值不等于缓冲区的模板值时通过测试
Opengl默认GL_LESS,kanzi默认always。
五:裁剪测试
当启用scissor test时,会将渲染时的视口做限制,也就是我们处理的像素范围做了限制。
六:混合
七:抗锯齿
目前了解的在kanzi中做抗锯齿都是从shader入手还有多重采样。