什么是着色器 Shader
本人就是图形学结课了,对 OpenGL着色器还有很多疑问嘿嘿
文章目录
- 为什么要有着色器
- vshader
- fshader
本文围绕 vshader 和 fshader 代码示例讲解。
(着色器代码取自本人简单OpenGL项目 https://github.com/DBWGLX/-OpenGL-3D-Lighting-and-Shadow-Modeling)
为什么要有着色器
着色器代码其实是给 GPU 跑的。
我们玩 3A 大作,每时每刻画面中大量角色物体可能都在交互变化,并且会因光源而表现不同(高光,明暗)。
时时刻刻计算这么多,CPU 几个核,再加MIPS(每秒百万指令)指令集也根本跑不完。
但是这些计算其实都是简单矩阵间计算,那我们可以换更多的简单核来跑,既不需要太复杂又足够计算 —— GPU
GPU 擅长做 大规模的矩阵运算
- vshader 负责把物体规划好【模型空间 -> 世界空间 -> 相机空间 -> 屏幕空间】
- fshader 结合光照和纹理,对显示器屏幕上的每个“像素点”计算最终颜色【给每个像素上色】
vshader
Vertex Shader 顶点着色器
【顶点着色器】是图形管线的第一阶段,作用是接收每个顶点的信息(如位置、颜色、法线、投影等),对其进行计算,并输出一些信息供后续的【片元着色器】使用。
比如三位建模的物体移动,模型的所有点、面都要移动,我们提供坐标给GPU让他来算位置。(当然还有透视投影,跟多关于 “投影和相机” 可以阅读以了解:https://blog.csdn.net/JK01WYX/article/details/143242785)
下面代码的 in 变量就是我们 cpp 程序提前绑定的,out 就是给 fshader 的,uniform(统一的)就是绑定好在vshader里 大家都用来计算的(相机坐标系,投影方式等)。
#version 330 core // GLSL(OpenGL Shading Language),版本是 #version 330 core,即 OpenGL 3.3 核心版本。// 顶点着色器
in vec3 vPosition; // 顶点位置,3D坐标
in vec3 vColor; // 颜色值(如 RGB)
in vec3 vNormal; // 法向量,通常用于光照计算
in vec2 vTexture; // 纹理坐标,通常范围在 [0, 1]// 传给片元着色器的变量
out vec3 position;
out vec3 normal;
out vec3 color;
out vec2 texCoord; // texture coordinate 纹理坐标// 模型变换矩阵、相机观察矩阵、投影矩阵
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{// 把顶点位置乘以模型矩阵,变换到世界空间。 // vec4() 是把 3D 坐标变成 4D 齐次坐标。vec4 v1 = model * vec4(vPosition, 1.0); // 由于model矩阵有可能为阴影矩阵,为了得到正确位置,我们需要做一次透视除法vec4 v2 = vec4(v1.xyz / v1.w, 1.0);// 先应用视图矩阵(把世界空间转换到相机空间),再应用投影矩阵vec4 v3 = projection* view * v2;// 最终设置到 gl_Position,这是 GPU 用来决定顶点【最终在屏幕上位置】的变量gl_Position = v3;// 最后:传递变量给片元着色器position = vec3(v2.xyz);normal = vec3( (model * vec4(vNormal, 0.0)).xyz ); // 法线向量是方向,不需要平移,所以 w = 0.0。color = vColor;texCoord = vTexture;}
fshader
我当时加载了三角面片和四角面片的模型,自己去做了适配。
甚至后面在 B站的模之屋 下载了多网格并各自带纹理的模型,去 Blender 转成 .obj 去加载 (当然失败,纹理这块)
其实也完全可以写多个着色器的。
#version 330 core// 光源结构体
struct Light{vec4 ambient; // 环境光颜色(整个空间的背景亮度)vec4 diffuse; // 漫反射颜色(光打到表面后均匀反射)vec4 specular; // 镜面高光颜色(发亮的高光)vec3 position; // 光源的位置// 控制光照距离衰减的三个参数float constant; // 常数项float linear; // 一次项float quadratic;// 二次项
};// 材质属性结构体
struct Material{// 物体本身对应三种反射光的颜色属性vec4 ambient;vec4 diffuse;vec4 specular;// 镜面反射的“锐利程度”(越大越亮 越小越模糊)float shininess;
};// In
in vec3 position;
in vec3 normal;
in vec3 color;
in vec2 texCoord;uniform vec3 eye_position; // 相机坐标
uniform Light light; // 光源uniform Material material; // 物体材质
uniform int isShadow; // 是否为阴影
uniform int hasTextureMap; // 是否使用纹理
uniform sampler2D texture; // 纹理采样器// 这是片元最终的颜色结果,会输出到屏幕上。
out vec4 fColor;void main()
{if (isShadow == 1) { // 如果当前是画阴影贴图,就直接涂成黑色,不用算光照。fColor = vec4(0.0, 0.0, 0.0, 1.0);}else {// 将顶点坐标、光源坐标和法向量转换到相机坐标系vec3 norm = (vec4(normal, 0.0)).xyz;vec3 N = normalize(norm); // 法向量vec3 L = normalize(light.position - position); // 光源方向//vec3 L = normalize(light.position); // 平行光vec3 V = normalize(eye_position - position); // 视角方向vec3 R = reflect(-L, N); // 反射方向vec3 H = normalize(L + V); // 半角向量(可选)vec4 I_a = light.ambient * material.ambient; // 环境光 = 光源的环境光 × 材质的环境反射能力// @TODO: Task2 计算系数和漫反射分量I_d // 漫反射 = max(光线方向·法线方向, 0) × 光的亮度 × 材质漫反射颜色float diffuse_dot = max(dot(L, N), 0.0);vec4 I_d = diffuse_dot * light.diffuse * material.diffuse;// @TODO: Task2 计算系数和镜面反射分量I_s // 镜面高光 = 视角方向·反射方向的夹角float specular_dot_pow = pow(max(dot(V, R), 0.0), material.shininess);// shininess 越大,高光越小越亮,表示光滑表面// float specular_dot_pow = pow( clamp(dot(V, R), 0.0, 1.0), material.shininess );vec4 I_s = specular_dot_pow * light.specular * material.specular;// 光在背面就不该出现高光效果if (dot(L, N) < 0.0) {I_s = vec4(0.0, 0.0, 0.0, 1.0);}fColor = vec4(color, 1.0);if (hasTextureMap==1) {//纹理基础颜色vec4 textureColor = texture2D(texture, texCoord);// 合并三个分量的颜色,修正透明度fColor = textureColor;}// 总结三种光照并输出颜色 : 最终颜色 = 材质色 × (环境光 + 漫反射 + 镜面反射)fColor *= I_a + I_d + I_s;fColor.a = 1.0; // 保证不透明}
}
这段 shader 是个非常典型的 Phong 光照模型 + 纹理融合 示例,非常值得你自己调试、注释、修改看看不同效果。