重生之——我成了一个分辨率仅为5的渲染器???
前言
其实一直都想写个啥来投稿, 可是完全想不到写什么话题, 于是一直拖啊拖啊, 随着截稿日一天一天接近, 再不写就寄了, 只好随便抓一个来写吧, 虽然是只个半成品, 好歹是个半成品, 主打一个压线操作 (XD)
快速预览
不废话, 首先来一段简单的演示动画, 快速的感受一下整个产品的功能

介绍
相信看完之后最大的感受就是:
啊?? 就这??
啊??
就这??"
啊??
就这??
"
没错的, “就这了”
有点简单是吧
不过按照惯例, 还是得介绍介绍的, 请看图:

大致上就很好理解, 这里就不啰嗦了
虽然写了详细的功能介绍, 不过感觉有点啰嗦, 不打算在这里展开
我把它挪到了文章的最后, 有需要请自行翻阅
体验
相信有了上面的介绍, 大家都明白如何使用, 感兴趣的朋友可以直接上手体验
因为服务器是暂时借用的, 所以你也可以
下载web打包在本地运行 (2.3 MB)
结语
好了以上就是本次分享的全部内容, 希望大家能喜欢, 也希望大家能提出宝贵的意见, 让我有所收获
(这段是ai写的)
示例合集
好了, 既然还接着往下看, 还是来几个简单的示例, (划掉)蹭蹭文章长度(划掉), 来看看这个小玩具有什么用
希望对渲染流程不是很理解的朋友, 能有所帮助
其实我也不是很懂, 所以有什么问题, 请大佬多多指点
[示例1]
先看动图

动图里, 分别建了3个图层, 分别是红蓝青
然后让3个图层分别错开, 也保留了一定重合度
最后调节了深度值
最后屏幕上, 颜色之间的重叠情况, 发生了改变
通过动图, 很容易就可以观察到一个结论
深度小的模型 --> 覆盖 --> 深度大的模型
这就是示例1所表达的内容
[示例2]
也是先看动图

动图里, 分别建了6个图层
然后每个图层, 都点亮了相同位置的一个, 重叠率100%
我们很容易就可以理解到, 他们肯定会相互重叠, 最后只有一个颜色亮起来
所以我们可以把屏幕看成只有1个像素, 就是 1x1 的分辨率
到底谁会保留到最后被渲染呢?
其实这个示例介绍的是
"渲染排序"
一起往下看看吧
阶段1
这个渲染器的设计里, 渲染的默认顺序是从上往下
也就是第一层, 浅蓝色的图层, 是最早被渲染的
接下来到了第二层, 黄色的图层
但是黄色图层因为深度和浅蓝色图层一样, 都是0.5, 所以它没有机会渲染了
接下来, 后续的其他图层, 深度都是0.5, 所以会遇到相同的情况
所以最终留在屏幕上的是浅蓝色
阶段2
让我们开始调整不同图层的深度

可以观察到, 随着深度增加, 被调整的图层被遮挡, 由下面深度更小的图层被渲染了
这是示例1里面我们就已经了解的情况
只是改为了1像素
相信应该是比较容易理解的
阶段3
这时候, 我们开关一下排序

可以观察到, 渲染结果没有区别, 但是 pixle draws 发生了变化
从 1 变成了 6
这说明了什么呢?
<关闭排序>
按照前面的理论
-
浅蓝色图层最早被渲染, 它的深度被调整为0.73, 此时屏幕上什么都没有, 所以绘制了一个浅蓝色, 并记下了深度
- 用专业的术语来讲,
颜色缓存被赋值为浅蓝色, 并且深度缓存被赋值为0.73,pixle draws + 1
-
黄色图层第二被渲染, 它的深度被调整为0.7, 比缓存的0.73小, 所以在屏幕中间绘制了一个黄色, 于是浅蓝色被黄色覆盖了
- 用专业的术语来讲,
颜色缓存被赋值为黄色, 并且深度缓存被赋值为0.7,pixle draws + 1
- 如此类推…
所以, 颜色缓存被赋值了6次, 最终为紫色, 深度缓存也被赋值6次, 最终为0.4, pixle draws === 6
<疑问>
在这个小小demo里, 确定要绘制一个点之后, 只是把颜色赋值过去就可以了
但是在实际的渲染中, 最终填上什么颜色, 就是我们熟悉的 fragment shader 阶段 所负责的, 这个颜色往往要经过采样多张贴图, 计算光照与迷雾等流程, 才能输出
如果经常因为深度遮挡, 被反反复复的覆盖, 那性能就被白白浪费了
所以按深度排序, 是常规的流程
<开启排序>
排序开启了之后, 在渲染之前, 会对所有的图层按深度来排序
总体来说:
深度越小, 越先渲染
所以, 虽然看上去浅蓝色图层是排在第一, 直觉上最先渲染的应该是浅蓝色图层
但是所有图层中, 紫色图层的深度是最小的, 所以最先渲染的应该是紫色图层
这个时候:
-
颜色缓存被赋值为紫色, 并且深度缓存被赋值为0.4,pixle draws + 1 -
然后, 就没有然后了, 因为遍历完
其他图层, 它们的深度, 没有小于0.4的, 所以根本就没有被渲染的机会,pixle draws也不会增加
在真实的渲染流程中, 就节约了非常多的 fragment shader 的运算
<小结>
这个示例其实没有什么用, 因为通常排序都是打开的, 普通的开发者基本上不需要关注
但是通过这个案例, 可以了解到渲染流程里有意思的小细节
这里放一个比较长的演示, 中间没有提到的内容, 可以尝试自行解释一下

有兴趣的朋友也可以运行工具, 自行探索
示例3
示例3里面, 就是写这个小工具最早的起因
X光效果
让我们来个正经一点的示例吧
场景绘制
我们先用3个层画一个背景
分别是天空, 云, 山
它们都很远, 所以深度都超过了0.8
↓↓↓

然后再画一棵树
这棵树是由 树干, 树叶 2个图层组成的
深度大概在0.5
↓↓↓

我希望玩家的角色可以被安放到遮挡复杂的场景中
所以再画另一棵树
这次画在左边
深度在0.2左右
↓↓↓

这时候, 再画一下玩家的角色
好像没多少位置了
就暂时…把玩家当作是一条黄色的毛毛虫吧
这条巨大的毛毛虫, 在2棵树之间穿行, 然后被路人偷拍了一张照片
照片中, 毛毛虫挡住了身后的背景, 自己也被另一棵树遮挡
嗯, 没有问题
↓↓↓

这个时候
玩家角色的一部分, 被遮挡了, 策划说: 这样不友好, 我们需要加一个'X光效果'
好吧, 说加就加
(如果不了解X光透视效果是如何制作的朋友, 建议先去了解一下, 因为有不少技术概念, 本文就不详细介绍了)
按照做法, 首先要增加一个pass
但是这里也没有pass的概念, 那就增加一个图层吧
图层里, 形状, 深度, 都是和玩家角色一样的
这代表了实际渲染的’X光效果’中, pass2 也是同用一个模型的, vertex shader 也没有什么骚操作
所以形状, 深度都是和 pass1 一样
至于颜色不一样, 颜色代表了 pass2 的透视颜色, 我们就定为显眼的洋红色吧
↓↓↓

可以观察到, 什么区别也没有!!
这是正常的, 因为我们还需要调整一个深度参数
将 'func' 从 '<' 改为 '>'
然后关掉 'write'
好了, 完成!
↓↓↓

甚至, 我们在最前面, 再加一个小山来遮挡
依然没有挡住我们的主角
↓↓↓

最后来一个动态流程, 方便观察
↓↓↓

顺便发一个开关排序的对比, 可以看到, pd 相差了不少
↓↓↓

如果有上手体验过的朋友, 可能会发现
你这 ‘write’ 关不关都行啊, 效果一样的
其实 ‘write’ 关掉的话, 可以从深度图里看到差别
↓↓↓

结语
感谢看到这里的朋友, 希望大家能喜欢这个小玩具, 也希望大家能提出宝贵的意见, 让我有所收获
(这段还是ai写的)
附: 详细的功能介绍

(还是这个图)
[屏幕区域]
虽然称为屏幕, 但其实就是一个 5x5 的像素阵列, 一共就25格
用我们日常的概念来讲, 就是分辨率为 5x5
分辨率故意做成比较小, 这便于我们学习和观察
[功能区域]
新增渲染
新增一个渲染对象
这个所谓的’渲染对象’, 在真实的渲染流程里面是不存在的, 这里只是抽象了一个概念
像素绘制次数
这里用的单位是’像素’, 就是每个像素点, 只要被绘制了一次(通过深度测试), 就+1
* 这不是平常叫的 Call Draw, 两者完全不是同一回事
(这个应该叫 Over Draw 吧, 懂的帮答一下)
启用排序
排序本身应该是渲染管线中的固定流程
不过这里单独做了一个开关, 可以感受一下, 没有排序的时候, 是怎样的一个情况, 它们又有什么差别
切换’颜色/深度’模式
切换屏幕显示的内容
颜色模式: 就是正常的渲染, 把颜色绘制到屏幕上
深度模式: 就是所谓的深度图了, 也叫深度缓存, 平常是看不到的, 有特殊的渲染需求的时候, 我们就会用到深度缓存, 也就是这篇文章的主要内容
[渲染队列]
注意这里也不是真实的渲染队列, 只是前面说的, '渲染对象’的队列
渲染队列中的渲染对象, 按照某种顺序进行排序, 然后逐个渲染
<模型设置>
为了简化复杂度, 凸显主要功能(深度测试), 这里把模型抽象成几个概念, 有点类似ps的图层
形状
形状就是指这个模型在屏幕上会显示到哪几个像素点, 这个过程在正规的渲染流程中, 经历了裁剪, 顶点着色器, 光栅化等步骤, demo中就直接抽象成可以随机涂画的图层了
颜色
颜色是指这个模型在屏幕上显示的颜色, 在正规的渲染流程中, 输出一个颜色, 通常需要采样贴图和计算光照等步骤, 所以同一个模型中, 不同像素的颜色是不相同的, 但是demo中就直接抽象成相同的值了
深度
深度是指模型离摄像机的距离, 这个值越小, 就越先被渲染, 在正规的渲染流程中, 不同像素的深度可以不相同, 但是demo中就直接抽象成一个值了
<深度设置>
深度设置是这个demo主要展示的功能, 所以都会以真实的功能来制作
深度测试开关
深度测试关闭时, 不会发生深度测试, 默认渲染
深度测试开启时, 会获取模型的深度值, 并与深度缓存做对比, 通过深度测试的像素, 才会绘制
深度写入开关
深度写入开启时, 通过深度测试之后, 会把当前的深度值写入缓存
深度写入关闭时, 即使通过深度测试, 也不会写入缓存
深度对比函数
提供了一下对比方式
-
GREATER: 当当前深度 > 深度缓存时, 通过深度测试 -
LESS: 当当前深度 < 深度缓存时, 通过深度测试 -
EQUAL: 当当前深度 = 深度缓存时, 通过深度测试 -
NOT_EQUAL: 当当前深度 != 深度缓存时, 通过深度测试 -
GREATER_EQUAL: 当当前深度 >= 深度缓存时, 通过深度测试 -
LESS_EQUAL: 当当前深度 <= 深度缓存时, 通过深度测试
后话
其实这个项目来自某天摸鱼的时候, 看到一个x光的shader教学, 虽然理论也不是很难, 但是一直想着能做一个可视化的小玩具, 那该多好
但是现在都是ai的时代了, 也许可以很简单的实现这个想法呢?
于是乎我让gpt给我写个vue来给我耍耍

想象很美好, 结果整出来这个玩意

其实这个已经是修补过的了, 关键是, 丑之余, 它还完全不能跑, 看来我还是多学点前端, 整玩具真的很需要
最后一气之下, 拿起 Cocos 搭起了游戏ui, 于是…
于是我就在ui逻辑里折腾了好久…
一来是我重新设计了交互方案, 去掉一堆进度条什么的
二来平时的游戏项目都很少做这种大量零碎选项的ui, 又想着以后写 demo 也许能舒服一点, 不如实现一套?
以至于整套交互都是自己实现的, 就用到了 Sprine, Label, 还有 ScrollView, Layout, Wedget 这些对齐组件
于是本身的渲染功能就拖到没完没了了
还好, 最后还是有结果了
但是做起来才发现, 流程越向正经渲染靠拢, 要补的概念就越多
比如最开始连排序也没做, 模型的概念也没有做, 就颜色/形状/深度啥的全丢一个对象里
于是又把结构改了一遍, 把模型和设置抽象了起来, 渲染流程也抽象起来
也许以后做成多pass之类的功能就能更方便的分离了吧
反正也不知道要做成啥样子, 就先这样吧, 后面也许还能加点别的