本文作者:StubbornHuang
版权声明:本文为站长原创文章,如果转载请注明原文链接!
原文标题:ThreeJS – 使用自定义Shader
原文链接:https://www.stubbornhuang.com/2353/
发布于:2022年09月13日 17:13:24
修改于:2022年09月13日 17:13:38

1 前言
在three.js已经预置了很多材质,比如MeshStandardMaterial、MeshPhongMaterial、MeshLambertMaterial等等,这些预置的材质其实在内部使用了Shader已经为我们创建了固定的渲染管线,我们可以直接使用而不用再去重新编写Shader重新实现这些渲染效果。
对熟悉OpenGL的特别是熟悉OpenGL3.3之后版本的可编程渲染管线的同学们来说,Shader不是一个陌生的东西。在图形学算法实现的过程中,我们就是需要编写一条一条冰冷的Shader代码来构建绚丽的渲染效果。如果涉及到性能调优,更需要在Shader层面对代码进行优化以达到更少的计算复杂度达到差不多的渲染效果。
2 Three.js中的Shader
本文以下的示例全部基于three.js r144版本。
2.1 ShaderMaterial和RawShaderMaterial
Three.js中提供两种自定义Shader的方式,一种是ShaderMaterial
,另一种则是RawShaderMaterial
。
ShaderMaterial
是Three.js将一些内置的attributes和uniforms已经添加到了Shader中,添加的内置变量如下,也可参考:https://threejs.org/docs/#api/zh/renderers/webgl/WebGLProgram
顶点着色器中内置的uniforms
// 模型矩阵 object.matrixWorld
uniform mat4 modelMatrix;
// 模型视图矩阵 camera.matrixWorldInverse * object.matrixWorld
uniform mat4 modelViewMatrix;
// 投影矩阵 camera.projectionMatrix
uniform mat4 projectionMatrix;
// 视图矩阵 camera.matrixWorldInverse
uniform mat4 viewMatrix;
// 模型视图矩阵的逆转置矩阵 inverse transpose of modelViewMatrix
uniform mat3 normalMatrix;
// 世界坐标下的摄像机坐标 camera position in world space
uniform vec3 cameraPosition;
顶点着色器中内置的attributes
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;
片段着色器中内置的attributes
uniform mat4 viewMatrix;
uniform vec3 cameraPosition;
而与ShaderMaterial
相比,RawShaderMaterial
不会将上述预置的uniforms和attributes自动添加到Shader中,而是需要我们自己去定义,这两种方式的区别会造成我们最后写出来的Shader不一样。
2.2 顶点着色器Vertex Shader和片段着色器Fragment Shaders
与OpenGL中一样,我们可以在Three.js中为ShaderMaterial
和RawShaderMaterial
都指定两个Shader,一个是顶点着色器Vertex Shader,另一个是片段着色器Fragment Shaders,简而言之,顶点着色负责模型顶点数据变换,片段着色器负责模型颜色生成。
在Three.js中,Shader有三种类型的变量:uniform, attribute和varying。
- uniform是所有顶点都具有相同的值的变量。 比如灯光,雾,和阴影贴图就是被储存在uniforms中的数据。 uniforms可以通过顶点着色器和片元着色器来访问。
- attribute与每个顶点关联的变量。例如,顶点位置,法线和顶点颜色都是存储在attributes中的数据。attributes 只可以在顶点着色器中访问。
- varying是从顶点着色器传递到片元着色器的变量。对于每一个片元,每一个varying的值将是相邻顶点值的平滑插值。
2.3 uniform与Javascript的类型对应
GLSL uniform | Javascript | size |
---|---|---|
float | Number | 1 |
vec2 | THREE.Vector2 | 2 |
vec3 | THREE.Vector3 | 3 |
vec3 | THREE.Color | 3 |
vec4 | THREE.Vector4 | 4 |
2.4 精度设置
如果在three.js中使用RawShaderMaterial
,我们需要设置默认数据类型的精度,如果不指定则会在编译Shader的时候报错,或者顶点着色器与片段着色器某个数据类型精度不一致也会报错,比如

上述图片显示的就是没有指定float精度的错误。
我们可以通过highp、mediump、lowp三个关键字对应精度的高、中、低,
顶点着色器默认精度
precision highp float;
precision highp int;
precision lowp sampler2D;
precision lowp samplerCube;
片元着色器默认精度
precision mediump int;
precision lowp sampler2D;
precision lowp samplerCube;
3 Shader使用示例
3.1 ShaderMaterial和RawShaderMaterial使用的差异
我们在这个示例中主要想实现以内置的THREE.BoxGeometry
的法向量作为片段着色器着色的颜色,效果如下图所示

ShaderMaterial的示例
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>three.js shader test</title>
<style type="text/css">
html,body {
margin: 0;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<script src="js/three.js-r144/build/three.js"></script>
<script src="js/three.js-r144/examples/js/controls/OrbitControls.js"></script>
<script id="vertexShader" type="x-shader/x-vertex">
varying vec3 vNormal;
void main() {
vNormal = normal;
vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewPosition;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
varying vec3 vNormal;
void main() {
gl_FragColor = vec4(vNormal,1.0);
}
</script>
<script>
let scene,camera,renderer,controls
function init() {
// 创建绘制上下文
renderer = new THREE.WebGLRenderer({
antialias:true,
//alpha:true
});
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.gammaOutput = true;
renderer.shadowMap.enabled = true;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.outputEncoding = THREE.sRGBEncoding
renderer.toneMappingExposure = 2.2;
document.body.appendChild(renderer.domElement)
// 创建场景
scene = new THREE.Scene();
//scene.background = new THREE.Color(0xFFdddd);
// 创建摄像头
camera = new THREE.PerspectiveCamera(60,window.innerWidth/window.innerHeight,0.1,5000);
camera.position.set(0,5,5);
controls = new THREE.OrbitControls(camera,renderer.domElement);
// 创建灯光
//// 半球光
hemiLight = new THREE.HemisphereLight(0xffeeb1, 0x080820, 1);
scene.add(hemiLight)
// 创建几何体
const geometry = new THREE.BoxGeometry(1, 1, 1)
uniforms = {
colorB: {type: 'vec3', value: new THREE.Color(0xACB6E5)},
colorA: {type: 'vec3', value: new THREE.Color(0x74ebd5)}
};
// 创建自定义shader
const material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
window.addEventListener( 'resize', onWindowResize, false );
}
function animate() {
requestAnimationFrame(animate)
controls.update()
renderer.render(scene,camera)
}
init();
animate()
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
</script>
</body>
</html>
RawShaderMaterial的示例
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>three.js shader test</title>
<style type="text/css">
html,body {
margin: 0;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<script src="js/three.js-r144/build/three.js"></script>
<script src="js/three.js-r144/examples/js/controls/OrbitControls.js"></script>
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec3 normal;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
varying vec3 vNormal;
void main() {
vNormal = normal;
vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewPosition;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision highp float;
varying vec3 vNormal;
void main() {
gl_FragColor = vec4(vNormal,1.0);
}
</script>
<script>
let scene,camera,renderer,controls
function init() {
// 创建绘制上下文
renderer = new THREE.WebGLRenderer({
antialias:true,
//alpha:true
});
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.gammaOutput = true;
renderer.shadowMap.enabled = true;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.outputEncoding = THREE.sRGBEncoding
renderer.toneMappingExposure = 2.2;
document.body.appendChild(renderer.domElement)
// 创建场景
scene = new THREE.Scene();
//scene.background = new THREE.Color(0xFFdddd);
// 创建摄像头
camera = new THREE.PerspectiveCamera(60,window.innerWidth/window.innerHeight,0.1,5000);
camera.position.set(0,5,5);
controls = new THREE.OrbitControls(camera,renderer.domElement);
// 创建灯光
//// 半球光
hemiLight = new THREE.HemisphereLight(0xffeeb1, 0x080820, 1);
scene.add(hemiLight)
// 创建几何体
const geometry = new THREE.BoxGeometry(1, 1, 1)
uniforms = {
colorB: {type: 'vec3', value: new THREE.Color(0xACB6E5)},
colorA: {type: 'vec3', value: new THREE.Color(0x74ebd5)}
};
// 创建自定义shader
const material = new THREE.RawShaderMaterial( {
uniforms: uniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
window.addEventListener( 'resize', onWindowResize, false );
}
function animate() {
requestAnimationFrame(animate)
controls.update()
renderer.render(scene,camera)
}
init();
animate()
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
</script>
</body>
</html>
通过上述ShaderMaterial和RawShaderMaterial程序代码的不同,由于ShaderMaterial已经内置了一些uniforms和attributes,所以不需要对一些常用的变量进行设置便可以使用,而RawShaderMaterial则需要自己设置数据类型精度以及投影视图矩阵等等才能实现相同的效果,不过RawShaderMaterial有利于实现自由度更高的Shader,不必拘束于three.js的内置变量。
3.2 将图片传入到Shader中作为模型贴图
首先我们需要使用THREE.TextureLoader
加载图片,然后将图片作为uniform传递给片段着色器进行模型表面贴图,基本上和OpenGL中一摸一样的流程,完整的代码如下:
这里我们使用ShaderMaterial,
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>three.js shader test</title>
<style type="text/css">
html,body {
margin: 0;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<script src="js/three.js-r144/build/three.js"></script>
<script src="js/three.js-r144/examples/js/controls/OrbitControls.js"></script>
<script id="vertexShader" type="x-shader/x-vertex">
varying vec3 vNormal;
varying vec2 vUV;
void main() {
vUV = uv;
vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewPosition;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
varying vec2 vUV;
uniform sampler2D uTexture;
void main() {
vec4 mapColor = texture2D(uTexture, vUV);
gl_FragColor = mapColor;
}
</script>
<script>
let scene,camera,renderer,controls
function init() {
// 创建绘制上下文
renderer = new THREE.WebGLRenderer({
antialias:true,
//alpha:true
});
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.gammaOutput = true;
renderer.shadowMap.enabled = true;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.outputEncoding = THREE.sRGBEncoding
renderer.toneMappingExposure = 2.2;
document.body.appendChild(renderer.domElement)
// 创建场景
scene = new THREE.Scene();
//scene.background = new THREE.Color(0xFFdddd);
// 创建摄像头
camera = new THREE.PerspectiveCamera(60,window.innerWidth/window.innerHeight,0.1,5000);
camera.position.set(0,5,5);
controls = new THREE.OrbitControls(camera,renderer.domElement);
// 创建灯光
//// 半球光
hemiLight = new THREE.HemisphereLight(0xffeeb1, 0x080820, 1);
scene.add(hemiLight)
// 创建几何体
const geometry = new THREE.BoxGeometry(1, 1, 1)
// 创建自定义shader
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('./texture.jpg')
uniforms = {
uTexture: {
value: texture
}
};
const material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
window.addEventListener( 'resize', onWindowResize, false );
}
function animate() {
requestAnimationFrame(animate)
controls.update()
renderer.render(scene,camera)
}
init();
animate()
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
</script>
</body>
</html>
贴图文件如下:

three.js效果如下:

参考链接
当前分类随机文章推荐
- ThreeJS - 摄像机绕X轴、绕Y轴、绕Z轴旋转指定角度 阅读575次,点赞1次
- ThreeJS - 获取当前使用的three.js的版本 阅读350次,点赞0次
- ThreeJS - 如何提升three.js的渲染效果?看完这篇你可能会有启发 阅读2723次,点赞0次
- ThreeJS - FBXLoader: TGA loader not found, creating placeholder texture for ... 阅读499次,点赞0次
- ThreeJS - 设置透明背景模仿L2Dwidget.js看板娘渲染效果 阅读376次,点赞0次
- ThreeJS - 修复摄像机近距离模型或者摄像机在某些观察角度3D模型部分或者全部不可见的问题 阅读364次,点赞0次
- ThreeJS - 使用Hdr环境贴图作为间接光照对模型进行渲染 阅读1170次,点赞3次
- ThreeJS - 直接设置Fbx模型的某个关节的位移和旋转值 阅读1654次,点赞0次
- ThreeJS - three.moudle.js报Uncaught SyntaxError:Unexpected token ‘export‘错误 阅读1797次,点赞0次
- ThreeJS - 使用自定义Shader 阅读544次,点赞2次
全站随机文章推荐
- 资源分享 – OpenGL Programming Guide (Ninth Edition) OpenGL红宝书英文第9版 英文高清PDF下载 阅读2313次,点赞1次
- 资源分享 - Isosurfaces - Geometry, Topology, and Algorithms 英文高清PDF下载 阅读1908次,点赞0次
- OpenCV - cv::VideoWriter::fourcc可支持的视频编码格式 阅读2279次,点赞0次
- Python – 解决opencv-python使用cv2.imwrite()保存中文路径图片失败的问题 阅读1321次,点赞0次
- Python - 配置Yolov5出现ImportError: cannot import name 'PILLOW_VERSION' from 'PIL'错误 阅读1153次,点赞0次
- Python - 不定长函数参数列表 阅读1941次,点赞0次
- 资源分享 - Image Objects - An Archaeology of Computer Graphics 英文高清PDF下载 阅读1661次,点赞1次
- Python - 判断一个字符串是否为json格式 阅读942次,点赞0次
- OpenCV - Mat与lplImage和CvMat的相互转换 阅读3610次,点赞0次
- FFmpeg - 音频处理基本概念以及音频重采样(采样率转换) 阅读4936次,点赞0次
评论
167