目录
1.什么是着色器
2.什么是GLSL
3.顶点着色器和片元着色器
3.1 顶点着色器Vertex shaders
3.2 片元着色器fragment shaders
4.着色器材质
4.1 什么是着色器材质
4.2 着色器材质的变量
4.3 着色器材质的简单使用
4.4 VScode配置着色器插件
5. 使用原始着色器(rawShaderMaterial)实现旗帜飘动效果
着色器(Shaders )是一种使用GLSL(OpenGL Shading Language)编写并在GPU上运行的程序。它们被用于定位几何体的每个顶点,并为该几何体的每个可见像素着色。使用“像素Pixel”来描述其实并不准确,因为渲染的每个点不一定与屏幕上的每个像素相匹配,因此我们更倾向于使用术语“片元fragment”。
之后我们会向着色器发送大量数据,如顶点坐标、网格变换、摄像机及其视野范围的信息、颜色、纹理、灯光、雾等参数。然后,GPU会按照着色器的指示处理所有的这些数据,接着几何体便出现在渲染中。
Shaders 也是一系列的指令,但是这些指令会对屏幕上的每个像素同时下达。也就是说,你的代码必须根据像素在屏幕上的不同位置执行不同的操作。就像活字印刷,你的程序就像一个 function(函数),输入位置信息,输出颜色信息,当它编译完之后会以相当快的速度运行。
下面一起来看“Hello world!”示例:
#ifdef GL_ES
precision mediump float;
#endif
uniform float u_time;
void main() {
gl_FragColor = vec4(0.533,0.650,1.000,1.000);
}
实现效果:
尽管这几行简单的代码看起来不像有很多内容,我们还是可以据此推测出一些知识点:
shader 语言 有一个 main
函数,会在最后返回颜色值。这点和 C 语言很像。
最终的像素颜色取决于预设的全局变量 gl_FragColor
。
这个类 C 语言有内建的变量(像gl_FragColor
),函数和数据类型。在本例中我们刚刚介绍了vec4
(四分量浮点向量)。之后我们会见到更多的类型,像 vec3
(三分量浮点向量)和 vec2
(二分量浮点向量),还有非常著名的:float
(单精度浮点型), int
(整型) 和 bool
(布尔型)。
如果我们仔细观察 vec4
类型,可以推测这四个变元分别响应红,绿,蓝和透明度通道。同时我们也可以看到这些变量是规范化的,意思是它们的值是从0到1的。之后我们会学习如何规范化变量,使得在变量间map(映射)数值更加容易。
另一个可以从本例看出来的很重要的类 C 语言特征是,预处理程序的宏指令。宏指令是预编译的一部分。有了宏才可以 #define
(定义)全局变量和进行一些基础的条件运算(通过使用 #ifdef
和 #endif
)。所有的宏都以 #
开头。预编译会在编译前一刻发生,把所有的命令复制到 #defines
里,检查#ifdef
条件句是否已被定义, #ifndef
条件句是否没有被定义。在我们刚刚的“hello world!”的例子中,我们在第2行检查了 GL_ES
是否被定义,这个通常用在移动端或浏览器的编译中。
float
类型在 shaders 中非常重要,所以精度非常重要。更低的精度会有更快的渲染速度,但是会以质量为代价。你可以选择每一个浮点值的精度。在第一行(precision mediump float;
)我们就是设定了所有的浮点值都是中等精度。但我们也可以选择把这个值设为“低”(precision lowp float;
)或者“高”(precision highp float;
)。
OpenGL着色语言(OpenGL Shading Language)是用来在OpenGL中着色编程的语言,也即开发人员写的短小的自定义程序,他们是在图形卡的GPU (Graphic Processor Unit图形处理单元)上执行的,代替了固定的渲染管线的一部分,使渲染管线中不同层次具有可编程性。比如:视图转换、投影转换等。GLSL(GL Shading Language)的着色器代码分成2个部分:Vertex Shader(顶点着色器)和Fragment(片断着色器),有时还会有Geometry Shader(几何着色器)。负责运行顶点着色的是顶点着色器。它可以得到当前OpenGL 中的状态,GLSL内置变量进行传递。GLSL其使用C语言作为基础高阶着色语言,避免了使用汇编语言或硬件规格语言的复杂性。
GLSL的变量命名方式与C语言类似。变量的名称可以使用字母,数字以及下划线,但变量名不能以数字开头,还有变量名不能以gl_作为前缀,这个是GLSL保留的前缀,用于GLSL的内部变量。当然还有一些GLSL保留的名称是不能够作为变量的名称的。
WebGL的着色器代码分为顶点着色器代码和片元着色器代码两部分,顶点着色器代码会在GPU的顶点着色器单元执行,片元着色器代码会在GPU的片元着色器单元执行,在WebGL渲染管线流程中,或者说GPU的渲染流程中,顶点着色器代码先执行处理顶点,得到一系列片元,然后再执行片元着色器代码处理片元。
顶点着色器(Vertex Shader)的作用是定位几何体的顶点。其思想是发送顶点位置、网格变换(如定位position、旋转rotation和缩放scale)、摄影机信息(如定位position、旋转rotation和视野fov)。然后,GPU将按照顶点着色器中的指示处理所有这些信息,以便将顶点投影到2D空间,该空间将成为我们的渲染render,也就是我们的画布canvas。
片元着色器的作用是为几何体的每个可见片元(像素)进行着色。
我们会创建片元着色器,可以通过使用uniform将数据(像是颜色)和着色器发送至GPU,之后GPU就会按照指令对每个片元进行着色。
参考:
1)顶点着色器收到系统传递给它的模型数据。
2)顶点着色器把模型数据处理成我们后续需要的数据进行输出(这些数据还包括纹理的UV坐标以及其他需要传递给片元着色器的数据)。
3)系统对顶点着色器输出的顶点数据进行插值,并将插值结果传递给片元着色器。
4)片元着色器根据这些插值结果计算最后屏幕上的像素颜色。
着色器材质(ShaderMaterial)是一个用GLSL编写的小程序 ,在GPU上运行。它能够提供 materials 之外的效果,也可以将许多对象组合成单个Geometry或BufferGeometry以提高性能。
每个着色器材质都可以指定两种不同类型的shaders,他们是顶点着色器和片元着色器(Vertex shaders and fragment shaders)。
shader中有三种类型的变量: uniforms, attributes, 和 varyings
关键字(变量类型) | 数据传递 | 声明变量 |
---|---|---|
attribute | javascript——>顶点着色器 | 声明顶点数据变量 |
uniform | javascript——>顶点、片元着色器 | 声明非顶点数据变量 |
varying | 顶点着色器——>片元着色器 | 声明需要插值计算的顶点变量 |
注意:在shader 内部,uniforms和attributes就像常量;你只能使用JavaScript代码通过缓冲区来修改它们的值。
炫彩蛋示例:
var geom = new THREE.SphereGeometry(10, 30, 20);
var mate = new THREE.ShaderMaterial({
vertexShader: `
varying vec3 vNormal;
void main() {
//将attributes的normal通过varying赋值给了向量vNormal
vNormal = normal;
//projectionMatrix是投影变换矩阵 modelViewMatrix是相机坐标系的变换矩阵 最后我们将y值乘以1.4得到了一个形如鸡蛋的几何体
gl_Position = projectionMatrix * modelViewMatrix * vec4( position.x, position.y * 1.4, position.z, 1.0 );
}
`,
fragmentShader: `
//片元着色器同样需要定义varying vec3 vNormal;
varying vec3 vNormal;
void main() {
//vNormal是一个已经归一化的三维向量
float pr = (vNormal.x + 1.0) / 2.0; //pr红色通道值范围为0~1
float pg = (vNormal.y + 1.0) / 2.0; //pg绿色通道值范围为0~1
float pb = (vNormal.z + 1.0) / 2.0; //pb蓝色通道值范围为0~1
gl_FragColor=vec4(pr, pg, pb, 1.0); //最后设置顶点颜色,点与点之间会自动插值
}
`
})
var mesh = new THREE.Mesh(geom, mate);
scene.add(mesh)
实现效果:
// 创建着色器材质
const shaderMaterial = new THREE.ShaderMaterial({
//顶点着色器
vertexShader: `
void main(){
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ) ;
}
`,
//片元着色器
fragmentShader: `
void main(){
gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);//rgba
}
`,
});
// 创建平面
const floor = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1, 64, 64),
shaderMaterial
);
console.log(floor);
scene.add(floor);
实现效果:
此外,在进行项目构建时,由于涉及较多着色器编写,建议对着色器文件单独进行编写,例如:
并在js文件中通过import导入使用
// 顶点着色器
import basicVertexShader from "../shader/basic/vertex.glsl";
// 片元着色器
import basicFragmentShader from "../shader/basic/fragment.glsl";
在shaderMaterial中调用着色器:
// 创建着色器材质
const shaderMaterial = new THREE.ShaderMaterial({
vertexShader: basicVertexShader,
fragmentShader: basicFragmentShader,
});
在插件商店中搜索Shader languages support for VS Code插件,可以对glsl等着色器语言进行语法支持。
fragment.glsl:
precision lowp float;精度
// highp -2^16 - 2^16
// mediump -2^10 - 2^10
// lowp -2^8 - 2^8
varying vec2 vUv;
varying float vElevation;
//导入采样纹理
uniform sampler2D uTexture;
void main(){
// gl_FragColor = vec4(vUv, 0.0, 1.0);//由uv渲染颜色(0,1)
// float height = vElevation + 0.05 * 10.0;
// gl_FragColor = vec4(1.0*height,0.0, 0.0, 1.0);
float height = vElevation + 0.05 * 20.0;//0~2
// 根据UV,取出对应的颜色
//根据uv进行采样
vec4 textureColor = texture2D(uTexture,vUv);
textureColor.rgb*=height;
gl_FragColor = textureColor;
}
vertex.glsl:
//精度
// highp -2^16 - 2^16
// mediump -2^10 - 2^10
// lowp -2^8 - 2^8
precision lowp float;
attribute vec3 position;
attribute vec2 uv;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
// 获取时间
uniform float uTime;
//传到片元着色器的
varying vec2 vUv;
//高度
varying float vElevation;
void main(){
vUv = uv;
vec4 modelPosition = modelMatrix * vec4( position, 1.0 );
// modelPosition.x += 1.0;
// modelPosition.z += 1.0;
// modelPosition.z += modelPosition.x;
//使用sin函数实现波浪效果
modelPosition.z = sin((modelPosition.x+uTime) * 10.0)*0.05 ;
modelPosition.z += sin((modelPosition.y+uTime) * 10.0)*0.05 ;
//把z的值传过去
vElevation = modelPosition.z;
gl_Position = projectionMatrix * viewMatrix * modelPosition ;
}
main.js:
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 顶点着色器
import basicVertexShader from "../shader/raw/vertex.glsl";
// 片元着色器
import basicFragmentShader from "../shader/raw/fragment.glsl";
// 目标:认识shader
// 初始化场景
const scene = new THREE.Scene();
// 创建透视相机
const camera = new THREE.PerspectiveCamera(
90,
window.innerHeight / window.innerHeight,
0.1,
1000
);
// 设置相机位置
// object3d具有position,属性是1个3维的向量
camera.position.set(0, 0, 2);
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
scene.add(camera);
// 加入辅助轴,帮助我们查看3维坐标轴
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 加载纹理
// 创建纹理加载器对象
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load("./texture/ca.jpeg");
// 创建原始着色器材质
const rawShaderMaterial = new THREE.RawShaderMaterial({
vertexShader: basicVertexShader,
fragmentShader: basicFragmentShader,
// wireframe: true,
side: THREE.DoubleSide,
//设置传输的数据
uniforms: {
uTime: {
value: 0,
},
uTexture: {
value: texture,
},
},
});
// 创建平面
const floor = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1, 64, 64),
rawShaderMaterial
);
console.log(floor);
scene.add(floor);
// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ alpha: true });
// 设置渲染尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 监听屏幕大小改变的变化,设置渲染的尺寸
window.addEventListener("resize", () => {
// console.log("resize");
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置渲染器的像素比例
renderer.setPixelRatio(window.devicePixelRatio);
});
// 将渲染器添加到body
document.body.appendChild(renderer.domElement);
// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼
controls.enableDamping = true;
// 设置自动旋转
// controls.autoRotate = true;
const clock = new THREE.Clock();
function animate(t) {
const elapsedTime = clock.getElapsedTime();
// console.log(elapsedTime);
rawShaderMaterial.uniforms.uTime.value = elapsedTime;
requestAnimationFrame(animate);
// 使用渲染器渲染相机看这个场景的内容渲染出来
renderer.render(scene, camera);
}
animate();
实现效果: