看了这么久的threejs了,今天使用着色器RawShaderMaterial绘出了第一个图形,特来记录下,例子比较简单,讲的也比较详细,相信不熟悉着色器编程的小伙伴们也能看懂哦。
首先了解编写本程序用到的GLSL基本知识点,webgl中着色器的介绍与流程之前有分享过:传送门
attribute变量只能在顶点着色器中使用,它是一个全局变量,被用来表示顶点信息,举例:
attribute vec3 position; // 存储坐标信息
attribute vec2 uv; // 贴图坐标
attribute vec4 a_Color // 颜色
uniform变量既可以在顶点着色器中使用,也可以在片元着色器中使用,它也是一个全局变量,可以是除了数组与结构体的任何类型,在顶点着色器和片元着色器定义了同名uniform变量时会被二者共享,即会被所有顶点和片元共用,它可以被用来存储变换矩阵,时间纹理等举例:
uniform mat4 modelMatrix; // 模型转换矩阵
uniform mat4 viewMatrix; // 视图矩阵
uniform mat4 projectionMatrix; // 投影矩阵
uniform float uTime; // 时间
vary它也是一个全局变量,与uniform不同的一点是:他必须同时在顶点着色器和片元着色器中定义同名同类型的varying变量,它的作用是把顶点着色器的数据传递给片元着色器,举例:
在顶点着色器,使用varying定义一个变量v_uv,它是二维向量类型,用它获取每个点的uv坐标
varying vec2 v_uv;
void main() {
v_uv = uv;
···
}
在片元着色器中接收来自顶点着色器中的uv信息,并把它作为rbga中的前俩个值
varying vec2 v_uv;
void main() {
gl_FragColor = vec4(v_uv, 0.0, 1.0);
}
在GLSL中需要指定精度以提高运行效率,减少内存损耗,可以在开头指定以下三个之一。
precision lowp float;
precision medium float;
precision highp float;
将一个(外部引入)模型的最终显示到二维屏幕上,实际经历了一系列模型转换过程,流程如下。后续我会单独记录一下流程原理,其中相机坐标系又叫做视图坐标系;目前只需要知道他们在threejs中是如何代表的:
在webgl中它们的类型和名称如下:
uniform mat4 modelMatrix; // 模型转换矩阵
uniform mat4 viewMatrix; // 视图矩阵
uniform mat4 projectionMatrix; // 投影矩阵
position // 模型坐标系中的坐标
Threejs中提供了ShaderMaterial和RawShaderMaterial两种材质可进行GLSL编写,ShaderMaterial内置了许多常用的GLSL变量,我们使用RawShaderMaterial从头编写。
在外部引入写好的着色器,基本场景的构建可以参考这里
import basicVertexShader from '../shader/vertexShader.glsl'
import basicFragmentShader from '../shader/fragmentShader.glsl'
···
const shaderMaterial = new THREE.RawShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader
})
在顶点着色器中,主要做了这么几件事:
顶点着色器内容:
attribute vec3 position; // 顶点的坐标信息
attribute vec2 uv; // 顶点的uv坐标信息
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
varying vec2 v_uv; // 要传递给片元着色器的数据
precision lowp float;
void main() {
v_uv = uv; // uv坐标信息
// gl_Position为每个点裁剪坐标,在RawShaderMaterial可以使用下式获取
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
在顶点着色器中,主要做了:
片元着色器内容:
varying vec2 v_uv; // 接收来自顶点着色器的信息
precision lowp float;
void main() {
gl_FragColor = vec4(v_uv, 0.0, 1.0); // 将来自顶点着色器的信息赋给颜色rg值(gl_FragColor 表示片元颜色)
}
最终效果如下:
threejs采用右手空间坐标系,红轴为X轴,绿轴为Y轴,在左下角uv坐标为(0.0,0.0),所以他的rgba就变成了(0.0,0.0,0.0,1.0)的纯黑色,同理右上角的颜色是rgba(1.0,1.0,1.0,1.0)的黄色。
在顶点着色器中,将模型坐标单独提取出来:
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * viewMatrix * modelPosition;
modelPosition就是我们说的模型坐标,即右手坐标系,注意它和空间直角坐标系是不同的。(图片来自网络)
对模型的xyz坐标进行处理,修改顶点着色器,对于每个点顶点:
void main() {
v_uv = uv; // uv坐标信息
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
modelPosition.x += 1.0;
modelPosition.y += 1.0;
modelPosition.z += 0.1 * sin(modelPosition.x * 10.0);
gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
threejs为了提高效率默认只渲染单面,需要把 side:THREE.DoubleSide给设置上
const shaderMaterial = new THREE.RawShaderMaterial({
// 投影矩阵 * 视图矩阵 * 模型矩阵 * 顶点坐标
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side:THREE.DoubleSide
})
对模型的片元着色器再进行处理,按照Z坐标的大小为其设置颜色:
顶点着色器:
attribute vec3 position;
attribute vec2 uv;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
varying vec2 v_uv;
varying float f_height;// 传递Z
precision lowp float;
void main() {
// v_uv = uv; // uv坐标信息
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
modelPosition.x += 1.0;
modelPosition.y += 1.0;
modelPosition.z += 0.1 * sin(modelPosition.x * 10.0);
f_height = modelPosition.z // 放入传递的数据
gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
片元着色器:
// varying vec2 v_uv;
varying float f_height; // 传递来的Z坐标
precision lowp float;
void main() {
float height = f_height + 1.0; // 创建变量接收数据
// gl_FragColor = vec4(v_uv, 0.0, 1.0);
gl_FragColor = vec4(height * 1.0, 0.0, 0.0, 1.0);
}
在RawSahderMaterial中,需要我们手动将时间传递给着色器,为此我们需要:
为此,需要对原有代码进行一些改造,同时也调整一下片元着色器的亮度。
const clock = useRef(new THREE.Clock()).current // 定义Three.Clock
const shaderMaterial = useRef(null) // 将材质提到全局
···
// 初始化地板
const initGeometry = () => {
// 原始着色器材质
shaderMaterial.current = new THREE.RawShaderMaterial({
// 投影矩阵 * 视图矩阵 * 模型矩阵 * 顶点坐标
vertexShader,
fragmentShader,
uniforms: {
uTime: { value: 0 }
},
side: THREE.DoubleSide
})
const floor = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1, 32, 32),
shaderMaterial.current
);
scence.add(floor)
}
···
// 渲染器执行渲染
const renderScene = useCallback(() => {
console.log('renderScene')
timer.current = window.requestAnimationFrame(() => renderScene())
controls.update();
const curElapsedTime = clock.getElapsedTime()
shaderMaterial.current.uniforms.uTime.value = curElapsedTime
// console.log(curElapsedTime,'---')
render.render(scence, camera);
}, [render])
顶点着色器:
precision lowp float;
attribute vec3 position;
attribute vec2 uv;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform float uTime; // 接收时间
varying vec2 v_uv;
varying float f_height;
void main() {
v_uv = uv; // uv坐标信息
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
modelPosition.x += 1.0;
modelPosition.y += 1.0;
modelPosition.z = 0.05 * sin((modelPosition.x + uTime)* 10.0);
modelPosition.z += 0.05 * sin((modelPosition.y + uTime)* 10.0);
f_height = modelPosition.z;
gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
以上就是基本使用了,流程还是比较简单的,有要看完整代码的朋友评论区留一下联系方式我会私发的哈。