本帖最后由 SQwatermark 于 2020-6-16 11:24 编辑

前言
Introduction
本文翻译自一个blog,博主总结了很多Minecraftforge模组制作相关的概念,非常简洁扼要。这篇有关光照的文章解决了我许多疑惑,在此翻译并分享给各位。

由于我是java菜鸟,对渲染这一块一窍不通,无法保证翻译完全准确,也无法保证原文的内容100%准确,希望各位能采各家之长,不可偏信,如有谬误,还请批评指正。

光照简述markdown.zip (747.87 KB, 下载次数: 11)
正文
Main body
光照简述 [1.14.4+]
Minecraft使用了三种不同的光照模型:
  • 无光照 - 通常用于GUI屏幕。
  • 物品光照 - 使用OpenGL光源模型,用于渲染GUI中的物品,例如物品栏中的物品。
  • 世界光照 - 用于渲染随着世界中的光照(天空的光照和附近的发光物的光照)而产生明暗变化的东西。


物品光照(Item Lighting)
物品光照使用两个光源来使物品看上去是三维的。当使用适当的渲染类型进行渲染时,它使用顶点法线信息执行着色。

世界光照(World Lighting)
Minecraft中最基础的世界光照模型由两部分组成;天空光照和方块光照。世界中的每个格子都有一个介于0 - 15之间的天空光照值和介于0 - 15之间的方块光照值。
  • 天空光照(Sky Light)是方块接收到的来自天空的光照,可能是直接的(方块直接暴露在天空下),也可能是间接的(方块并非暴露在天空下,但是靠近暴露在天空下的方块)。
  • 方块光照(Block Light)是方块从附近的光源(火把、萤石之类)接收的光照。

原版为每个方块计算天空光照和方块光照。基本概念是:
  • 每个方块都有一个不透明度,决定了它从六个相邻方块接收了多少光。低不透明度(例如1)光照等级从邻近的方块传递到这个方块时降低了1,高不透明度意味着光照等级相应地降低更多。这个计算过程对于天空光照和方块光照是分别进行的。
  • 一些方块本身会发光(例如萤石),如果它发出的光的光照等级超过了邻近方块传递过来的方块光照等级,那么光照等级将会设定为它发出的光的光照等级。
  • 如果方块暴露在天空之下,或者上方只有透明方块,那么它的天空光照等级是15。(译注:请注意,天空光照与时间无关,它在夜晚不会减弱。)


方块光照传播的二维例子,不透明度为2,。天空光照以相同方式传播。

方块被渲染时,方块光照和天空光照等级被用于设置方块的亮度。步骤如下:
  • 方块光照和天空光照等级被合并为一个32bit整数。前16bit包含天空光照乘16,后16bit包含方块光照乘16。
  • 这个合成的“混合亮度”值,被渲染器解释为一张名为光照贴图的材质中的两个16位坐标(也就是说,[方块光照,天空光照]),参考下方的图片。(译注:这个光照贴图和用OptiFine制作的光照贴图有很大区别,甚至似乎不太兼容,其中的原因只能等我详细研究原版光照渲染代码和OptiFine光照贴图的处理之后才能解释了)
  • 当方块的面被渲染时,首选绘制出材质给出的图案,然后使用光照贴图中正确的像素(也就是[BlockLight, SkyLight]坐标处的像素 )对其进行调整。(这个步骤叫Multitexturing)


光照贴图的材质(OpenGlHelper.lightmapTexUnit),有16x16像素点。每个像素点对应一个方块光照和天空光照的组合。

为什么Minecraft要这么做呢?至少有两条很好的理由:
  • 分别处理方块光照和天空光照可以获得更真实的光照效果。看看光照贴图,你会看到方块光照提供了一种红色或者棕色的变化,但是天空光照没有。这模拟了由火把之类的方块发出的黄色光线。
  • 光照贴图很容易更新,以改变整体的光照效果 - 例如随着一天中的时刻的明暗变化(昼夜和日出日落);火把光线的闪烁效果;或者玩家喝下一瓶夜视药水。这个更新由EntityRenderer.updateLightMap()执行。


基于面的光照效果(Face-dependent lighting effects)
除了上述的每个方块的照明,Minecraft还可以为方块的每个面进行一些额外的计算,来增强照明的真实性:
1-不同朝向的面的亮度(Direction-dependent face brightness)
这个方法根据面的朝向,用不同的光照强度渲染六个面:
  • 顶面(Y+)为最大亮度
  • 底面(Y-)为50%亮度
  • Z轴上的面(Z+、Z-=南、北)为80%亮度
  • Z轴上的面(X+、X-=东、西)为60%亮度



基于面的光照效果:左侧关闭,右侧开启

这种照明被用于每个面;当材质被渲染时,在原材质上乘一个 "colour" 值(在这个情况下,这个值是不同程度的灰色)
在一些情况下,调用者还会传入一个RGB颜色乘数,(例如在水下,方块被给予了一个蓝**调(tint)),乘以上述的%强度。
面的光照还会受到[方块光照,天空光照]混合亮度的影响。下面的图标总结了这些步骤。

展示Minecraft应用光照效果的示意图:从上至下为:不同面的不同亮度,颜色乘数,[方块光照,天空光照]贴图
2- 环境光遮蔽(Ambient Occlusion)
环境光遮蔽是一种基于附近的方块计算表面的光照的方法。这儿有Minecraft如何实现这个方法的简介。简而言之,对于每个顶点,计算出三个毗邻的方块和接触这个面的方块的平均光照强度。在下方的例子中,对于这个用红色圈起来的顶点,是以白色显示的这三个方块和这个面正上方的方块(未显示)的平均亮度。如果这四个方块都是透明的,”环境光遮蔽“光照强度将会是1.0。每个不透明方块都会降低顶点接收到的光照 - 如果四个方块都是不透明的那么这个强度将会是0.2。”环境光遮蔽“值会与顶点的颜色乘数相乘。顶点的混合亮度被计算为四个相邻方块的平均值。(还有一些其他有关对角方块是否被计算在内的细节,但是你已经知道了基本的思路)在绘制面时,OpenGL提供了平滑,所以环境光遮蔽在不同顶点上的产生的阴影在整个平面上被平滑了。

环境光遮蔽算法。从0fps改编的图表

更多细节:
当一个四边形在世界中被渲染时,可能被应用一些不同的光照效果:
  • “漫反射光线”(阴影),亮度随着四边形的朝向改变。
  • multitexturing亮度:方块光照+天空光照
  • 环境光遮蔽(如果开启了的话):四边形的四个顶点将会:

    • 变得更暗,如果不透明方块挨着这个顶点
    • 对每个顶点应用方块光照+天空光照,而不是对整个面使用同一的数据


方块光照+天空光照按照四边形所处的位置计算:
  • 在立方体内部=这个立方体本身的光照
  • 在立方体的边缘:这个面毗邻的方块的光照

方块光照+天空光照通常计算为:
  • 对于isEmissiveRendering()==true的方块,取二者的最大值
  • 对于其他方块,天空光照和方块光照之和,或者blockState.getLightValue(),取二者的最大值


一些有趣的原版类
BlockModelRenderer::renderModelSmooth (ambient occlusion rendering)
BlockModelRenderer::renderModelFlat (no ambient occlusion)
ILightReader
LightTexture (the multitexture used for blocklight+skylight)
WorldRenderer::getPackedLightmapCoords

更多信息