绘制不断变色的台体,BufferGeometry和ShaderMaterial的黄金组合(three.js实战5)
Posted 点燃火柴
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了绘制不断变色的台体,BufferGeometry和ShaderMaterial的黄金组合(three.js实战5)相关的知识,希望对你有一定的参考价值。
使用BufferGeometry和ShaderMaterial绘制不断变色的台体
1. 前言与demo效果
上一篇文章学习了如何使用BufferGeometry 向缓存中传输几何体的顶点坐标、面索引、顶点颜色、顶点法向量、顶点UV等, 也提到了使用ShaderMaterial,但最后没有具体说明,这一次专门说一说BufferGeometry和ShaderMaterial如何配合使用,先来看看做的demo效果
2. 如何使用ShaderMaterial
这里只介绍一下使用ShaderMaterial材质最主要的三个属性,vertexShader,fragmentShader和uniforms
2.1 vertexShader属性
vertexShader 表示顶点着色器,通过该属性可以定义GLSL语言编写的程序,可以直接运行在GPU上,通过顶点着色器,可以处理几何体体上的每一个顶点。如果对WebGL有一定的了解,这部分会非常容易理解
2.2 fragmentShader属性
fragmentShader 表示片元着色器,与顶点着色器一样,也可以自定义GLSL程序,可以用来处理几何体上的每一个像素。
2.3 uniforms属性
这个属性是javascript程序与着色器程序之间的通道,通过uniforms属性向顶点着色器和片元着色器传递uniform变量,顶点着色器和片元着色器接收到对应的变量后,可以根据需要做相应的处理
创建ShaderMaterial材质 的官方示例如下
var material = new THREE.ShaderMaterial( {
uniforms: {
time: { value: 1.0 },
resolution: { value: new THREE.Vector2() }
},
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
3. demo实现要点
3.1 创建ShaderMaterial材质
创建ShaderMaterial主要有三大块,设置uniforms属性,顶点着色器和片元着色器,示例中unforms定义了三个变量r、g、b分别用了传递颜色分量,顶点着色器是通用的着色器,使用投影矩阵,模型视图矩阵和顶点坐标相乘,赋值给内置变量gl_Position,片元着色器中使用接收到的r、g、b颜色分量创建vec4类型的颜色赋值给内置变量 gl_FragColor,具体实现如下:
let boxMaterialShader = {
uniforms: {
"r": {
type: "f",
value: 1.0
},
"g": {
type: "f",
value: 0.6
},
"b": {
type: "f",
value: 0.8
}
},
vertexShader: `
void main(){
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float r;
uniform float g;
uniform float b;
void main(){
gl_FragColor = vec4(vec3(r,g,b),0.9);
}
`
};
//创建ShaderMaterial材质
const material = new THREE.ShaderMaterial({
uniforms: boxMaterialShader.uniforms,
vertexShader: boxMaterialShader.vertexShader,
fragmentShader: boxMaterialShader.fragmentShader,
side: THREE.DoubleSide
})
material.needsUpdate = true
3.2 绘制台体和边框线
首先创建BufferGeometry几何体,然后设置position属性和index属性,使用创建好的ShaderMaterial材质绘制台体,然后克隆一份几何体对象,设置边框线的索引然后使用LineSegments绘制边框线,具体如下:
function initModel() {
//创建BufferGeometry几何体
const bufferGeom = new THREE.BufferGeometry()
//设置顶点信息
const positions = new Float32Array([
-5.0, 2.0, -5.0,
5.0, 2.0, -5.0,
5.0, 2.0, 5.0,
-5.0, 2.0, 5.0,
-6.0, -2.0, -6.0,
6.0, -2.0, -6.0,
6.0, -2.0, 6.0,
-6.0, -2.0, 6.0,
])
bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3))
//设置索引信息
const indexs = new Uint16Array([
0, 1, 2, //上
0, 2, 3, //上
4, 5, 6, //下
4, 6, 7, //下
0, 3, 4, //左
3, 4, 7, //左
1, 2, 5, //右
2, 5, 6, //右
2, 3, 6, //前
3, 6, 7, //前
0, 1, 4, //后
1, 4, 5, //后
])
bufferGeom.index = new THREE.BufferAttribute(indexs, 1)
//创建Mesh对象并添加到场景
const mesh = new THREE.Mesh(bufferGeom, material)
scene.add(mesh)
// 绘制边框
const lineGeom = bufferGeom.clone() //克隆一份BufferGeometry对象
//设置绘制线的索引
const lineIndexs = new Uint16Array([
0, 1, 1, 2, 2, 3, 3, 0,
4, 5, 5, 6, 6, 7, 7, 4,
0, 4, 1, 5, 2, 6, 3, 7
])
lineGeom.index = new THREE.BufferAttribute(lineIndexs, 1)
//创建绘制线的材质
const lineMaterial = new THREE.LineBasicMaterial({
color: 0x018BF5,
side: THREE.DoubleSide
})
//使用LineSegments绘制线段,渲染时mode为gl.LINES
const line = new THREE.LineSegments(lineGeom, lineMaterial)
scene.add(line)
}
3.3 render中更新uniforms变量
在render函数中不断更新uniforms变量,顶点着色器和片元着色器接收的新的变量后重新绘制,就可以得到不断变色的台体
//更新传给着色器材质的uniform变量
boxMaterialShader.uniforms.r.value += 0.01
boxMaterialShader.uniforms.g.value += 0.01
if (boxMaterialShader.uniforms.r.value > 1) {
boxMaterialShader.uniforms.r.value = 0.1
}
if (boxMaterialShader.uniforms.g.value > 1) {
boxMaterialShader.uniforms.g.value = 0.1
}
4. demo代码
<!DOCTYPE html>
<html>
<head>
<title>Example 01 - flowing</title>
<script type="text/javascript" src="../three/build/three.js"></script>
<script type="text/javascript" src="../three/examples/js/controls/OrbitControls.js"></script>
<script type="text/javascript" src="../three/examples/js/loaders/OBJLoader.js"></script>
<script type="text/javascript" src="../three/examples/js/libs/stats.min.js"></script>
<!-- EffectComposer要先于其他后期处理文件引入,否则其他文件无法正确引入 -->
<script type="text/javascript" src="../three/examples/js/postprocessing/EffectComposer.js"></script>
<script type="text/javascript" src="../three/examples/js/postprocessing/RenderPass.js"></script>
<script type="text/javascript" src="../three/examples/js/postprocessing/ShaderPass.js"></script>
<script type="text/javascript" src="../three/examples/js/shaders/CopyShader.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="Stats-output"></div>
<div id="WebGL-output"></div>
<script type="text/javascript">
let stats, controls, composer, customPass;
let camera, scene, renderer;
let boxMaterialShader = {
uniforms: {
"r": {
type: "f",
value: 1.0
},
"g": {
type: "f",
value: 0.6
},
"b": {
type: "f",
value: 0.8
}
},
vertexShader: `
void main(){
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float r;
uniform float g;
uniform float b;
void main(){
gl_FragColor = vec4(vec3(r,g,b),0.9);
}
`
};
//创建ShaderMaterial材质
const material = new THREE.ShaderMaterial({
uniforms: boxMaterialShader.uniforms,
vertexShader: boxMaterialShader.vertexShader,
fragmentShader: boxMaterialShader.fragmentShader,
side: THREE.DoubleSide
})
material.needsUpdate = true
function initScene() {
scene = new THREE.Scene();
}
function initCamera() {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(30, 80, 80)
camera.lookAt(new THREE.Vector3(0, 0, 0));
}
function initLight() {
//添加环境光
var ambientLight = new THREE.AmbientLight(0xffffff);
scene.add(ambientLight);
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(5, 10, 20);
spotLight.castShadow = true;
scene.add(spotLight);
}
function initModel() {
//创建BufferGeometry几何体
const bufferGeom = new THREE.BufferGeometry()
//设置顶点信息
const positions = new Float32Array([
-5.0, 2.0, -5.0,
5.0, 2.0, -5.0,
5.0, 2.0, 5.0,
-5.0, 2.0, 5.0,
-6.0, -2.0, -6.0,
6.0, -2.0, -6.0,
6.0, -2.0, 6.0,
-6.0, -2.0, 6.0,
])
bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3))
//设置索引信息
const indexs = new Uint16Array([
0, 1, 2, //上
0, 2, 3, //上
4, 5, 6, //下
4, 6, 7, //下
0, 3, 4, //左
3, 4, 7, //左
1, 2, 5, //右
2, 5, 6, //右
2, 3, 6, //前
3, 6, 7, //前
0, 1, 4, //后
1, 4, 5, //后
])
bufferGeom.index = new THREE.BufferAttribute(indexs, 1)
//创建Mesh对象并添加到场景
const mesh = new THREE.Mesh(bufferGeom, material)
scene.add(mesh)
// 绘制边框
const lineGeom = bufferGeom.clone() //克隆一份BufferGeometry对象
//设置绘制线的索引
const lineIndexs = new Uint16Array([
0, 1, 1, 2, 2, 3, 3, 0,
4, 5, 5, 6, 6, 7, 7, 4,
0, 4, 1, 5, 2, 6, 3, 7
])
lineGeom.index = new THREE.BufferAttribute(lineIndexs, 1)
//创建绘制线的材质
const lineMaterial = new THREE.LineBasicMaterial({
color: 0x018BF5,
side: THREE.DoubleSide
})
//使用LineSegments绘制线段,渲染时mode为gl.LINES
const line = new THREE.LineSegments(lineGeom, lineMaterial)
scene.add(line)
}
function initRender() {
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
})
//renderer.shadowMap.enabled = true // 显示阴影
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x0f2d48, 1) // 设置背景颜色
renderer.toneMapping = THREE.ACESFilmicToneMapping;
document.getElementById("WebGL-output").appendChild(renderer.domElement);
}
//初始化轨道控制器
function initControls() {
clock = new THREE.Clock() // 创建THREE.Clock对象,用于计算上次调用经过的时间
controls = new THREE.OrbitControls(camera, renderer.domElement)
//controls.autoRotate = true // 是否自动旋转
}
function init() {
initScene();
initCamera();
initLight();
initRender();
initStats();
initControls();
initModel();
render();
}
function updateFun() {
stats.update();
const delta = clock.getDelta() // 获取自上次调用的时间差
controls.update(delta) // 相机更新
//更新传给着色器材质的uniform变量
boxMaterialShader.uniforms.r.value += 0.01
boxMaterialShader.uniforms.g.value += 0.01
if (boxMaterialShader.uniforms.r.value > 1) {
boxMaterialShader.uniforms.r.value = 0.1
}
if (boxMaterialShader.uniforms.g.value > 1) {
boxMaterialShader.uniforms.g.value = 0.1
}
}
function render() {
updateFun()
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function initStats() {
stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
document.getElementById("Stats-output").appendChild(stats.domElement);
}
window.onload = init;
</script>
</body>
</html>
以上是关于绘制不断变色的台体,BufferGeometry和ShaderMaterial的黄金组合(three.js实战5)的主要内容,如果未能解决你的问题,请参考以下文章
绘制彩色立方体,使用attribute变量向着色器传值,BufferGeometry和ShaderMaterial配合使用(three.js实战6)