文章目录
前些篇:
这一篇:实现类似折射的效果
本人才疏学浅,如有什么错误,望不吝指出。
扯一些闲话:
前几天和小舅子玩了玩 PS4:
- 《Hell Driver》(地狱老司机,这个游戏数值做得太渣,想不通一个游戏数值制作这么差)
- 《Alienation》(异化,和 我比较喜欢的《Dead Nation》同开发商的产品,操作手感很不错)
- 《God of War III》(战神3,之前没空玩,然后花了两天通关中等难度,总体感觉战神3操作手感不是很好,战神4之前视频通关过,但也没时间去玩,战神4的剧情、渲染、操作手感,都有着非常大的提升、序幕也很期待与雷神索尔之战:God of War V,战神5)
现在回过头来看看这些以前很羡慕游戏开发,现在感觉渲染技术上来说越来越简单了,因为有了一些基础积累(注意是:基础)。
但是要制作这些大作,对场景、特效、动作/动画、音效、剧情、关卡、数值有着巨量工作,最后是调试也是巨量的、反复的。
战神3 我连最后所有的 谢幕信息都看完了,开发阵容真的强大,投入很大!
先看看效果
下面是反射+折射 各50%,再加上半透明 Blend SrcAlpha OneMinusSrcAlpha
的融混效果
目前这个效果是只对 Skybox 天空盒 作为环境贴图的反射、折射的效果,所以可以看到,气球猫 与 球体 之间没有反射 和 折射
折射现象
引用 百度百科:折射 一段话:
光的折射:光从一种透明介质斜射入另一种透明介质时,传播方向一般会发生变化,这种现象叫光的折射。理解:光的折射与光的反射一样都是发生在两种介质的交界处,只是反射光返回原介质中,而折射光则进入到另一种介质中,由于光在在两种不同的物质里传播速度不同,故在两种介质的交界处传播方向发生变化,这就是光的折射。注意:在两种介质的交界处,既发生折射,同时也发生反射。反射光光速与入射光相同 ,折射光光速与入射光不同。
引用 wiki 斯涅尔定律 的一段话:
当光波从一种介质传播到另一种具有不同折射率的介质时,会发生折射现象,其入射角与折射角之间的关系,可以用斯涅尔定律(Snell’s Law)来描述。斯涅尔定律是因荷兰物理学家威理博·斯涅尔而命名,又称为“折射定律”。
在光学里,光线跟踪科技应用斯涅尔定律来计算入射角与折射角。在实验光学与宝石学里,这定律被应用来计算物质的折射率。对于具有负折射率的负折射率超材料(metamaterial),这定律也成立,允许光波因负折射角而朝后折射。
斯涅尔定律表明,当光波从介质1传播到介质2时,假若两种介质的折射率不同,则会发生折射现像,其入射光和折射光都处于同一平面,称为“入射平面”,并且与界面法线的夹角满足如下关系:
n 1 sin θ 1 = n 2 sin θ 2 n 1 sin θ 1 = n 2 sin θ 2 {\displaystyle n_{1}\sin \theta _{1}=n_{2}\sin \theta _{2}}n_{1}\sin \theta _{1}=n_{2}\sin \theta _{2} n1sinθ1=n2sinθ2n1sinθ1=n2sinθ2;
其中, n 1 n 1 、 n 2 n 2 {\displaystyle n_{1}}n_{1}、{\displaystyle n_{2}}n_{2} n1n1、n2n2分别是两种介质的折射率, θ 1 θ 1 {\displaystyle \theta _{1}}\theta_1 θ1θ1和 θ 2 θ 2 {\displaystyle \theta _{2}}\theta_2 θ2θ2分别是入射光、折射光与界面法线的夹角,分别叫做“入射角”、“折射角”。
这公式称为“斯涅尔公式”。
斯涅尔定律可以从费马原理推导出来,也可以从惠更斯原理、平移对称性或麦克斯韦方程组推导出来。
再引用 立方体贴图 里的一张图:
refract 函数的使用
参考 CG refract 函数 - nvidia 上的 cg refract 文档,说明不够详细,只是将函数的定义原型给出了
可以看出来:
float3 refract(float3 i, float3 n, float eta);
refract 的参数说明:
- float3 i 是入射角
- float3 n 是表面法线
- float eta 是 源物质折射率 与 目标物质折射率 之比
前两个参数都很好理解,第三个参数需要了解 源物质折射率 和 目标物质折射率 的区别
假设 空气折射率为:1.0 ,水的折射率为 1.33 ,那么从 空气 到 水 中传入的 refract 的第三个参数为: 1.0 / 1.33,那么可得知 源物质折射率 为:空气,目标物质折射率 为:水。
反推,如果从水底中观察水面上的物质时,refract 的第三个参数就应该传入:1.33/1.00 (水的折射率 / 空气折射率)
上面的折射率都是科学家通过实验总结出来的规律值
常用物质折射率表
材质 | 折射率 |
---|---|
空气 | 1.00 |
水 | 1.33 |
冰 | 1.309 |
玻璃 | 1.52 |
钻石 | 2.42 |
这次我们的 shader 代码有调整:
Shader 调整
my_lighting.glsl
添加材质属性
// 材质属性 - 很多的属性可以合并到其他没有满4个分量的属性,但学习目的,为了可读性高,可以分开来
struct Material_t {
float Glossy; // 光滑度
vec3 Emission; // 自发光颜色
vec3 DiffuseK; // 漫反射系数
vec3 SpecularK; // 高光系数
int UseTexAlpha; // 是否使用纹理 alpha 值
float Alpha; // 当 UseTexAlpha == 0,则使用该值
float ReflectionK; // 反射系数,反射越强,折射越小,所以也会影响折射效果
float RefractionK; // 折射效果强度
float SrcRefractionK; // 源物质折射率
float DstRefractionK; // 目标物质折射率
};
uniform Material_t Material;
这一次主要添加了:
int UseTexAlpha; // 是否使用纹理 alpha 值
float Alpha; // 当 UseTexAlpha == 0,则使用该值
float RefractionK; // 折射效果强度
float SrcRefractionK; // 源物质折射率
float DstRefractionK; // 目标物质折射率
添加了着色器信息结构体
// Shading Info 着色信息
struct ShadingInput_t {
vec4 tex_col;
vec3 normal;
vec3 worldPos;
};
添加了计算折射的函数
// 只计算折射
void calculate_refraction(vec3 worldNormal, vec3 viewDir, vec3 albedo, out vec3 refraction) {
refraction = albedo; // 环境 或 自身反射率 颜色
if (ClearType == CLEAR_TYPE_SKYBOX) { // 天空盒的
vec3 R = refract(-viewDir, worldNormal, Material.SrcRefractionK / Material.DstRefractionK); // 外部可以优化,传入一个 SrcRefraction / DstRefraction 的值,但这里学习用,写清晰一些
refraction = texture(SkyboxTex, R).rgb;
} else if (ClearType == CLEAR_TYPE_COLOR) { // 清理颜色的
refraction = ClearColor; // 不用计算反射
}
}
提取了着色函数
// 着色计算
void shading(ShadingInput_t input_info, out vec4 output_col) {
vec3 albedo = input_info.tex_col.rgb;
float alpha = Material.UseTexAlpha == 0 ? Material.Alpha : input_info.tex_col.a;
vec3 worldNormal = normalize(input_info.normal); // 世界坐标法线再次归一化一次,因为插值之后可能会导致不是归一化的值
vec3 viewDir = getWorldViewDir(input_info.worldPos); // 顶点坐标 指向 镜头坐标 的方向
vec3 reflection, refraction;
calculate_reflection(worldNormal, viewDir, albedo, reflection); // 计算反射
calculate_refraction(worldNormal, viewDir, albedo, refraction); // 计算折射
vec3 ambient = getAmbient(albedo);
vec3 emission = Material.Emission;
vec3 diffuse = vec3(0);
vec3 specular = vec3(0);
phong_illumination(worldNormal, viewDir, input_info.worldPos, diffuse, specular); // 计算光照
#define SHADING_TYPE 0 // 理想的反射折射方式,但这种方式依赖理想的环境贴图
// #define SHADING_TYPE 1 // 将 emission、specular 分离出来的方式
#if SHADING_TYPE == 0
vec3 combinedCol = min(vec3(1.0), ambient + emission + diffuse * albedo + specular);
combinedCol = mix(combinedCol, reflection, Material.ReflectionK); // 应用反射
combinedCol = mix(combinedCol, refraction, Material.RefractionK); // 应用折射
#elif SHADING_TYPE == 1
vec3 combinedCol = min(vec3(1.0), ambient + diffuse * albedo);
combinedCol = mix(combinedCol, reflection, Material.ReflectionK); // 应用反射
combinedCol = mix(combinedCol, refraction, Material.RefractionK); // 应用折射
combinedCol += emission + specular;
#endif
output_col = vec4(combinedCol, alpha);
}
外部调用
// jave.lin - testing_refraction.frag
#version 450 compatibility
#extension GL_ARB_shading_language_include : require
#include "/Include/my_global.glsl"
#include "/Include/my_lighting.glsl"
// interpolation - 插值数据
in vec2 fUV0; // uv 坐标
in vec3 fNormal; // 顶点法线
in vec3 fWorldPos; // 世界坐标
uniform sampler2D main_tex;
void main() {
vec4 out_col;
ShadingInput_t input_info;
input_info.tex_col = texture(main_tex, fUV0);
input_info.normal = fNormal;
input_info.worldPos = fWorldPos;
shading(input_info, out_col);
gl_FragColor = out_col;
}
References
- 立方体贴图
- CG refract 函数的推导 - 比较浅显易懂
- CG refract 函数的推导 - 没那么浅显易懂
- CG refract 函数 - nvidia 上的 cg refract 文档,说明不够详细,只是将函数的定义原型给出了
- 斯涅尔定律 - 中文 wiki
- Snell’s_law - 英文 wiki