我在场景中加载了一个STL文件,并为Phong材质应用了单一颜色。
我想找到一种方法,可以对这个网格的材质应用两种颜色,并在Z轴上应用渐变效果,就像下面的例子。Gradient Vase]1
我觉得我可能需要引入着色器,但我还没有在three.js中做到这一点。
我在场景中加载了一个STL文件,并为Phong材质应用了单一颜色。
我想找到一种方法,可以对这个网格的材质应用两种颜色,并在Z轴上应用渐变效果,就像下面的例子。Gradient Vase]1
我觉得我可能需要引入着色器,但我还没有在three.js中做到这一点。
基于UV的简单渐变着色器:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
camera.position.set(13, 25, 38);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
var canvas = renderer.domElement
document.body.appendChild(canvas);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var geometry = new THREE.CylinderBufferGeometry(2, 5, 20, 32, 1, true);
var material = new THREE.ShaderMaterial({
uniforms: {
color1: {
value: new THREE.Color("red")
},
color2: {
value: new THREE.Color("purple")
}
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`,
fragmentShader: `
uniform vec3 color1;
uniform vec3 color2;
varying vec2 vUv;
void main() {
gl_FragColor = vec4(mix(color1, color2, vUv.y), 1.0);
}
`,
wireframe: true
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
render();
function resize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resize(renderer)) {
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
html,
body {
height: 100%;
margin: 0;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
display;
block;
}
<script src="https://cdn.jsdelivr.net/npm/three@0.115.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/controls/OrbitControls.js"></script>
基于坐标的简单渐变着色器:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
camera.position.set(13, 25, 38);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
var canvas = renderer.domElement
document.body.appendChild(canvas);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var geometry = new THREE.CylinderBufferGeometry(2, 5, 20, 16, 4, true);
geometry.computeBoundingBox();
var material = new THREE.ShaderMaterial({
uniforms: {
color1: {
value: new THREE.Color("red")
},
color2: {
value: new THREE.Color("purple")
},
bboxMin: {
value: geometry.boundingBox.min
},
bboxMax: {
value: geometry.boundingBox.max
}
},
vertexShader: `
uniform vec3 bboxMin;
uniform vec3 bboxMax;
varying vec2 vUv;
void main() {
vUv.y = (position.y - bboxMin.y) / (bboxMax.y - bboxMin.y);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`,
fragmentShader: `
uniform vec3 color1;
uniform vec3 color2;
varying vec2 vUv;
void main() {
gl_FragColor = vec4(mix(color1, color2, vUv.y), 1.0);
}
`,
wireframe: true
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
render();
function resize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resize(renderer)) {
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
html,
body {
height: 100%;
margin: 0;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/three@0.115.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/controls/OrbitControls.js"></script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
var canvas = renderer.domElement
document.body.appendChild(canvas);
var geom = new THREE.TorusKnotGeometry(2.5, .5, 100, 16);
var rev = true;
var cols = [{
stop: 0,
color: new THREE.Color(0xf7b000)
}, {
stop: .25,
color: new THREE.Color(0xdd0080)
}, {
stop: .5,
color: new THREE.Color(0x622b85)
}, {
stop: .75,
color: new THREE.Color(0x007dae)
}, {
stop: 1,
color: new THREE.Color(0x77c8db)
}];
setGradient(geom, cols, 'z', rev);
function setGradient(geometry, colors, axis, reverse) {
geometry.computeBoundingBox();
var bbox = geometry.boundingBox;
var size = new THREE.Vector3().subVectors(bbox.max, bbox.min);
var vertexIndices = ['a', 'b', 'c'];
var face, vertex, normalized = new THREE.Vector3(),
normalizedAxis = 0;
for (var c = 0; c < colors.length - 1; c++) {
var colorDiff = colors[c + 1].stop - colors[c].stop;
for (var i = 0; i < geometry.faces.length; i++) {
face = geometry.faces[i];
for (var v = 0; v < 3; v++) {
vertex = geometry.vertices[face[vertexIndices[v]]];
normalizedAxis = normalized.subVectors(vertex, bbox.min).divide(size)[axis];
if (reverse) {
normalizedAxis = 1 - normalizedAxis;
}
if (normalizedAxis >= colors[c].stop && normalizedAxis <= colors[c + 1].stop) {
var localNormalizedAxis = (normalizedAxis - colors[c].stop) / colorDiff;
face.vertexColors[v] = colors[c].color.clone().lerp(colors[c + 1].color, localNormalizedAxis);
}
}
}
}
}
var mat = new THREE.MeshBasicMaterial({
vertexColors: THREE.VertexColors,
wireframe: true
});
var obj = new THREE.Mesh(geom, mat);
scene.add(obj);
render();
function resize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resize(renderer)) {
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
obj.rotation.y += .01;
requestAnimationFrame(render);
}
html,
body {
height: 100%;
margin: 0;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
display;
block;
}
<script src="https://cdn.jsdelivr.net/npm/three@0.115.0/build/three.min.js"></script>
实际上,你可以选择使用哪种方式:着色器、顶点颜色、纹理等。
MeshPhongMaterial
的功能,可以尝试扩展该材质。vec4 diffuseColor = vec4( diffuse, opacity );
在学习《着色器之书》或其他教程后,您会了解到可以通过使用规范化因子(介于0,1之间的数字)混合两种颜色。
这意味着您可以将此行更改为以下内容:
vec4 diffuseColor = vec4( mix(diffuse, myColor, vec3(myFactor)), opacity);
你可以像这样扩展着色器
const myFactor = { value: 0 }
const myColor = {value: new THREE.Color}
myMaterial.onBeforeCompile = shader=>{
shader.uniforms.myFactor = myFactor
shader.uniforms.myColor = myColor
shader.fragmentShader = `
uniform vec3 myColor;
uniform float myFactor;
${shader.fragmentShader.replace(
vec4 diffuseColor = vec4( diffuse, opacity );
vec4 diffuseColor = vec4( mix(diffuse, myColor, vec3(myFactor)), opacity);
)}
`
myFactor.value
时,你的对象的颜色应该从myMaterial.color
变为myColor.value
。myFactor
。我喜欢使用UV的囚犯解决方案。它完全是用JavaScript完成的,并且非常容易在此着色器中连接。其他方法可能需要更多的着色器工作。vec4 diffuseColor = vec4( mix(diffuse, myColor, vec3(vUv.y)), opacity);
new PhongMaterial({color})
,即没有提供任何纹理,则着色器将在没有vUv
的情况下编译。
有许多条件会导致它编译并对您有用,但我不确定它们是否会破坏其他东西:#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )
因此,添加类似以下内容:
myMaterial.defines = {USE_MAP:''}
vUv
变量在您的着色器中可用。这样,您就可以让冯氏材质的所有光线影响材质,只需更改基础颜色即可。r111
)中,他们添加了USE_UV:''
以启用UV。myMaterial.defines = {USE_MAP:''}
。 - prisoner849.map
属性将纹理添加到材质中。或者,如果您想要它在没有灯光的情况下“发光”,则可以将其分配给.emissiveMap
属性。
.boundingBox
中的.min
和.max
值来进行统一处理。 - prisoner849