新智元原创
来源:Github
编辑:元子
Shader,是运行在GPU上的程序,中文称为着色器。它的主要用途是对三维物体进行着色处理,对光与影进行计算,以及控制纹理颜色的呈现等,最终,将游戏引擎中的几何数据转化为屏幕上的模型、场景以及特效。
吃鸡、农药、战地、塞尔达…都离不开着色器,着色器被誉为照亮虚拟世界的"魔法"。 着色器还可以用来做后期处理,类似PS。
着色程序是一整套编译好并链接在一起的着色器的集合。着色器shader的编写需要使用着色语言GL Shader Language(GLSL),GLSL的语法与C语言很类似。
着色器编程不光是对开发技能的考验,更是对程序员想象力的挑战,以至于有说法称会写Shader的程序员是站在食物链顶端的人。想成为游戏开发高手,着色器编程是必备技能。
有兴趣在3D游戏中添加纹理,光照,阴影,法线贴图,环境光遮蔽了吗?好极了!今天新智元为大家带来一个Github项目,从零开始教会大家进行3D游戏着色。
以下一系列着色技术,都是具有高可移植的技巧,无论是Godot还是Unity都将适用。通过这些技术,你的游戏视觉效果将提升到新的高度。
对于着色器之间的粘合剂,作者选择了神器Panda3D游戏引擎和OpenGL着色语言(GLSL)。
Panda3D是一个强大的渲染引擎。核心渲染模块基于C++开发。Panda3D提供了Python的脚本化实用接口。
本项目code在以下环境下测试通过。
本文只对该repo做一个大致介绍,具体代码及讲解请参阅项目地址:
https://github.com/lettier/3d-game-shaders-for-beginners.git
运行示例代码需要先build
如果你想运行示例代码,首先要先build。根据不同平台安装对应版本的Panda3D。其次,Clone项目repo,进入目录,开始编译。
Linux:
g++ \ -c main.cxx \ -o 3d-game-shaders-for-beginners.o \ -std=gnu++11 \ -O2 \ -I/usr/include/python2.7/ \ -I/usr/include/panda3d/
编译完成后执行:
g++ \ 3d-game-shaders-for-beginners.o \ -o 3d-game-shaders-for-beginners \ -L/usr/lib/panda3d \ -lp3framework \ -lpanda \ -lpandafx \ -lpandaexpress \ -lp3dtoolconfig \ -lp3dtool \ -lp3pystub \ -lp3direct \ -lpthread
Mac:
clang++ \ -c main.cxx \ -o 3d-game-shaders-for-beginners.o \ -std=gnu++11 \ -g \ -O2 \ -I/usr/include/python2.7/ \ -I/Developer/Panda3D/include/
编译完成后执行:
clang++ \ 3d-game-shaders-for-beginners.o \ -o 3d-game-shaders-for-beginners \ -L/Developer/Panda3D/lib \ -lp3framework \ -lpanda \ -lpandafx \ -lpandaexpress \ -lp3dtoolconfig \ -lp3dtool \ -lp3pystub \ -lp3direct \ -lpthread
坐标系统
开始插手着色器代码之前,需要对3D物体的坐标系统有所了解。和在立体几何的坐标系意义,绘制3D物体也是需要x、y、z三个坐标轴的值。
将定义好的坐标轴的值转换为实际绘制的坐标,需要经过五个坐标系统的转换。
模型空间
相对于原点 (0,0,0) 而自定义的起始坐标点。
世界空间
世界空间就是当所有物体一起绘制、仍然相对于原点的、更大的一个坐标系。可以防止模型出现扎堆儿情况。
观察空间
将世界空间的坐标转化为摄像机的视角所观察到的空间坐标。
裁剪空间
根据我们的需要来裁剪一定范围内的物体,而在这个范围之外的坐标就会被忽略掉,实质上还是进行坐标的操作。
屏幕空间
将坐标投射到屏幕上。
渲染到纹理
渲染到纹理(Render To Texture, RTT)是现在很多特效里面都会用到的一项很基本的技术,实现起来很简单,也很重要。
渲染到纹理是为了实现一些特殊的效果,比如一个光滑的球体,它应该是可以反射周围环境的,这个时候就需要先渲染到纹理。
在此设置中,示例代码执行以下操作:
纹理和光照(Lighting)
纹理涉及使用UV坐标将一些颜色或一些其他类型的矢量映射到片段。 U和V的范围从0到1。每个顶点都获得一个UV坐标,并在顶点着色器中输出。
完成lighting涉及到计算和组合环境光、漫反射光、镜面光和发射光方面。示例代码使用Phong lighting。Diffuse代码:
// ...
float diffuseIntensity = max(dot(normal, unitLightDirection), 0.0);
if (diffuseIntensity > 0) { // ... }
// ...
法线贴图
法线贴图(英语:Normal mapping)是一种模拟凹凸处光照效果的技术,是凸凹贴图的一种实现。法线贴图可以在不添加多边形的前提下,为模型添加细节。常见的使用场景是为低多边形模型改善外观、添加细节,此时的法线贴图一般根据高多边形模型或高度贴图生成。
顶点代码:
// ...
uniform mat3 p3d_NormalMatrix;
// ...
in vec3 p3d_Normal;
// ...
in vec3 p3d_Binormal;in vec3 p3d_Tangent;
// ...
vertexNormal = normalize(p3d_NormalMatrix * p3d_Normal); binormal = normalize(p3d_NormalMatrix * p3d_Binormal); tangent = normalize(p3d_NormalMatrix * p3d_Tangent);
// ...
描边
描边着色器需要一个输入纹理,用于检测边缘中的颜色。此输入纹理的候选者包括材质的漫反射颜色、漫反射贴图的颜色、顶点法线,甚至法线贴图的颜色。
uniform struct { vec4 diffuse;} p3d_Material;
out vec4 fragColor;
void main() { vec3 diffuseColor = p3d_Material.diffuse.rgb; fragColor = vec4(diffuseColor, 1);}
雾化、全屏泛光
雾(fog,或在Blender中称为mist)将雾气效果添加到场景中,提供神秘感和柔化。
// ...
uniform struct p3d_FogParameters { vec4 color ; float start ; float end ; } p3d_Fog;
// ...
Panda3D提供了一个很好的数据结构,可以保存所有fog参数,你也可以手动将其传递给着色器。
Bloom有时候也叫Glow效果,中文一般叫做“全屏泛光”,可以使得发光物体看起来更逼真。
屏幕空间环境光遮蔽(SSAO)
环境光遮蔽(AO,ambient occlusion),大致上指的是几何物体的拐角处,因为受光不全面(被相邻的面挡光/遮蔽),导致变暗。屏幕环境光遮蔽技术使用了屏幕空间场景的深度而不是真实的几何体数据来确定遮蔽量。这一做法相对于真正的环境光遮蔽不但速度快,而且还能获得很好的效果,使得它成为近似实时环境光遮蔽的标准。
下面动图展示了用AO和没用AO的区别。用了AO,物体拐角的地方会变暗看起来就更逼真。
景深
景深(英语:Depth of field, DOF)景深是指相机对焦点前后相对清晰的成像范围。在光学中,尤其是录影或是摄影,是一个描述在空间中,可以清楚成像的距离范围。
虽然透镜只能够将光聚到某一固定的距离,远离此点则会逐渐模糊,但是在某一段特定的距离内,影像模糊的程度是肉眼无法察觉的,这段距离称之为景深。
景深浅则模糊范围大,虚化效果明显。反之则虚化效果减弱。焦外代码示例:
// ...
vec4 result = vec4(0);
for (int i = 0; i < size2; ++i) { x = size - xCount; y = yCount - size;
result += texture ( blurTexture , texCoord + vec2(x * separation, y * separation) );
xCount -= 1; if (xCount < countMin) { xCount = countMax; yCount -= 1; } }
result = result / size2;
// ...
色调分离和像素化
色调分离是指一幅图像原本是由紧紧相邻的渐变色阶构成,被数种突然的颜色转变所代替。这一种突然的转变,亦称作“跳阶”。色调分离其实就是用来制造分色效果。
将3D游戏像素化使他看起来很有趣,并可以节省时间,不必手动创建所有的像素艺术。和色调分离相结合,可以打造真正的复古外观。
参考链接:
https://github.com/lettier/3d-game-shaders-for-beginners.git