在我的着色器中丢失片段
Lost fragments in my shader
我想在 Threejs 中做一个瓷砖系统:绿色代表地面/蓝色代表水。
我在 PlaneBufferGeometry 上使用着色器。
这是我目前的情况:
Relevant code :
- JS: variable
chunk
and function DoPlaneStuff()
(both at the beginning)
- HTML: vertex and fragment shader
var chunk = {
// number of width and height segments for PlaneBuffer
segments: 32,
// Heightmap: 0 = water, 1 = ground
heightmap: [
[1, 0, 0],
[1, 1, 0],
[1, 0, 1],
],
// size of the plane
size: 40
};
function DoPlaneStuff() {
var uniforms = {
heightmap: {
type: "iv1",
// transform the 2d Array to a simple array
value: chunk.heightmap.reduce((p, c) => p.concat(c), [])
},
hmsize: {
type: "f",
value: chunk.heightmap[0].length
},
coord: {
type: "v2",
value: new THREE.Vector2(-chunk.size / 2, -chunk.size / 2)
},
size: {
type: "f",
value: chunk.size
}
};
console.info("UNIFORMS GIVEN :", uniforms);
var shaderMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById("v_shader").textContent,
fragmentShader: document.getElementById("f_shader").textContent
});
var plane = new THREE.Mesh(
new THREE.PlaneBufferGeometry(chunk.size, chunk.size, chunk.segments, chunk.segments),
shaderMaterial
);
plane.rotation.x = -Math.PI / 2;
scene.add(plane);
}
// --------------------- END OF RELEVANT CODE
window.addEventListener("load", Init);
function Init() {
Init3dSpace();
DoPlaneStuff();
Render();
}
var camera_config = {
dist: 50,
angle: (5 / 8) * (Math.PI / 2)
}
var scene, renderer, camera;
function Init3dSpace() {
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({
antialias: true,
logarithmicDepthBuffer: true
});
camera = new THREE.PerspectiveCamera(
50,
window.innerWidth / window.innerHeight,
0.1,
1000
);
this.camera.position.y = camera_config.dist * Math.sin(camera_config.angle);
this.camera.position.x = 0;
this.camera.position.z = 0 + camera_config.dist * Math.cos(camera_config.angle);
this.camera.rotation.x = -camera_config.angle;
var light = new THREE.HemisphereLight(0xffffff, 10);
light.position.set(0, 50, 0);
scene.add(light);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function Render() {
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js"></script>
<!-- VERTEX SHADER -->
<script id="v_shader" type="x-shader/x-vertex">
// size of the plane
uniform float size;
// coordinates of the geometry
uniform vec2 coord;
// heightmap size (=width and height of the heightmap)
uniform float hmsize;
uniform int heightmap[9];
varying float colorValue;
void main() {
int xIndex = int(floor(
(position.x - coord.x) / (size / hmsize)
));
int yIndex = int(floor(
(-1.0 * position.y - coord.y) / (size / hmsize)
));
// Get the index of the corresponding tile in the array
int index = xIndex + int(hmsize) * yIndex;
// get the value of the tile
colorValue = float(heightmap[index]);
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<!-- FRAGMENT SHADER -->
<script id="f_shader" type="x-shader/x-fragment">
varying float colorValue;
void main() {
// default color is something is not expected: RED
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
// IF WATER
if (colorValue == 0.0) {
// BLUE
gl_FragColor = vec4( 0.0, 0.0, 1.0, 1.0 );
}
// IF GROUND
if (colorValue == 1.0) {
// GREEN
gl_FragColor = vec4( 0.1, 0.6, 0.0, 1.0 );
}
}
</script>
如您所见,它几乎可以正常工作,但我有这些红线将绿色和蓝色区域分开,但我不明白为什么。
我将这些红色碎片称为“丢失的碎片”,因为它们没有映射到任何图块,我不明白为什么。
我只能注意到 chunk.segments
的值越大(这是几何体的高度和宽度段的数量),我的红线就越细。
我想知道如何在绿色和蓝色区域之间而不是红色区域之间进行渐变填充。
红线由一些顶点位于地砖中而其他顶点位于水砖中的三角形组成。然后 GPU 沿三角形对 colorValue
进行插值,生成值从 0 到 1 的平滑梯度,而不是您可能期望的急剧变化。
对此有多种解决方案。您可以更改着色器中的条件以根据中点选择颜色:如果 colorValue
< 0.5,则输出蓝色,否则输出绿色。但是,如果您决定以后需要更多的图块类型,那将不会很好地工作。更好的解决方案是以所有三角形的所有顶点都位于单个图块中的方式生成几何体。这将涉及将位于图块边界上的顶点加倍。您还可以将 flat
插值限定符添加到 colorValue
,但更难控制三角形最终使用的顶点属性。
...我刚刚注意到您确实想要一个渐变而不是一个陡峭的台阶。那更容易。您需要将颜色选择代码从片段着色器移动到顶点着色器,并且只是 return 在片段着色器中生成的插值颜色。
我想在 Threejs 中做一个瓷砖系统:绿色代表地面/蓝色代表水。
我在 PlaneBufferGeometry 上使用着色器。
这是我目前的情况:
Relevant code :
- JS: variable
chunk
and functionDoPlaneStuff()
(both at the beginning)- HTML: vertex and fragment shader
var chunk = {
// number of width and height segments for PlaneBuffer
segments: 32,
// Heightmap: 0 = water, 1 = ground
heightmap: [
[1, 0, 0],
[1, 1, 0],
[1, 0, 1],
],
// size of the plane
size: 40
};
function DoPlaneStuff() {
var uniforms = {
heightmap: {
type: "iv1",
// transform the 2d Array to a simple array
value: chunk.heightmap.reduce((p, c) => p.concat(c), [])
},
hmsize: {
type: "f",
value: chunk.heightmap[0].length
},
coord: {
type: "v2",
value: new THREE.Vector2(-chunk.size / 2, -chunk.size / 2)
},
size: {
type: "f",
value: chunk.size
}
};
console.info("UNIFORMS GIVEN :", uniforms);
var shaderMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById("v_shader").textContent,
fragmentShader: document.getElementById("f_shader").textContent
});
var plane = new THREE.Mesh(
new THREE.PlaneBufferGeometry(chunk.size, chunk.size, chunk.segments, chunk.segments),
shaderMaterial
);
plane.rotation.x = -Math.PI / 2;
scene.add(plane);
}
// --------------------- END OF RELEVANT CODE
window.addEventListener("load", Init);
function Init() {
Init3dSpace();
DoPlaneStuff();
Render();
}
var camera_config = {
dist: 50,
angle: (5 / 8) * (Math.PI / 2)
}
var scene, renderer, camera;
function Init3dSpace() {
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({
antialias: true,
logarithmicDepthBuffer: true
});
camera = new THREE.PerspectiveCamera(
50,
window.innerWidth / window.innerHeight,
0.1,
1000
);
this.camera.position.y = camera_config.dist * Math.sin(camera_config.angle);
this.camera.position.x = 0;
this.camera.position.z = 0 + camera_config.dist * Math.cos(camera_config.angle);
this.camera.rotation.x = -camera_config.angle;
var light = new THREE.HemisphereLight(0xffffff, 10);
light.position.set(0, 50, 0);
scene.add(light);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function Render() {
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js"></script>
<!-- VERTEX SHADER -->
<script id="v_shader" type="x-shader/x-vertex">
// size of the plane
uniform float size;
// coordinates of the geometry
uniform vec2 coord;
// heightmap size (=width and height of the heightmap)
uniform float hmsize;
uniform int heightmap[9];
varying float colorValue;
void main() {
int xIndex = int(floor(
(position.x - coord.x) / (size / hmsize)
));
int yIndex = int(floor(
(-1.0 * position.y - coord.y) / (size / hmsize)
));
// Get the index of the corresponding tile in the array
int index = xIndex + int(hmsize) * yIndex;
// get the value of the tile
colorValue = float(heightmap[index]);
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<!-- FRAGMENT SHADER -->
<script id="f_shader" type="x-shader/x-fragment">
varying float colorValue;
void main() {
// default color is something is not expected: RED
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
// IF WATER
if (colorValue == 0.0) {
// BLUE
gl_FragColor = vec4( 0.0, 0.0, 1.0, 1.0 );
}
// IF GROUND
if (colorValue == 1.0) {
// GREEN
gl_FragColor = vec4( 0.1, 0.6, 0.0, 1.0 );
}
}
</script>
如您所见,它几乎可以正常工作,但我有这些红线将绿色和蓝色区域分开,但我不明白为什么。 我将这些红色碎片称为“丢失的碎片”,因为它们没有映射到任何图块,我不明白为什么。
我只能注意到 chunk.segments
的值越大(这是几何体的高度和宽度段的数量),我的红线就越细。
我想知道如何在绿色和蓝色区域之间而不是红色区域之间进行渐变填充。
红线由一些顶点位于地砖中而其他顶点位于水砖中的三角形组成。然后 GPU 沿三角形对 colorValue
进行插值,生成值从 0 到 1 的平滑梯度,而不是您可能期望的急剧变化。
对此有多种解决方案。您可以更改着色器中的条件以根据中点选择颜色:如果 colorValue
< 0.5,则输出蓝色,否则输出绿色。但是,如果您决定以后需要更多的图块类型,那将不会很好地工作。更好的解决方案是以所有三角形的所有顶点都位于单个图块中的方式生成几何体。这将涉及将位于图块边界上的顶点加倍。您还可以将 flat
插值限定符添加到 colorValue
,但更难控制三角形最终使用的顶点属性。
...我刚刚注意到您确实想要一个渐变而不是一个陡峭的台阶。那更容易。您需要将颜色选择代码从片段着色器移动到顶点着色器,并且只是 return 在片段着色器中生成的插值颜色。