重要的是 roundX 被四舍五入 (round) 到最接近的整数,而不是截断:
highp int roundX = int(round(vertexPosition.x / squareSize));
或
highp int roundX = int(vertexPosition.x / squareSize + 0.5 * sign(vertexPosition.x));
一种可能的解决方案是通过dFdy 沿视口的y 轴获得vertexPosition.xy 的偏导数。
vertexPosition.xy 的偏导数的长度给出了模型空间中 2 个片段之间的距离。因此可以定义线的最小粗细:
vec2 dy = dFdy(vertexPosition.xy);
float minWidth = length(dy);
float w = step(max(lineWidth, minWidth), abs(remainderX));
fragColor = mix(vec4(1.0, 0.0, 0.0, 1.0), vec4(0.8, 0.8, 0.8, 1.0), w);
对于更平滑的线条,您必须插入线条颜色和底色。如果abs(remainderX) 介于min(lineWidth, minWidth) 和max(lineWidth, minWidth) 之间,则进行插值。使用smoothstep 进行插值。例如:
highp int roundX = int(round(vertexPosition.x / squareSize));
highp float remainderX = vertexPosition.x - float(roundX)*squareSize;
vec2 dy = dFdy(vertexPosition.xy);
float minWidth = length(dy);
float w = smoothstep(min(lineWidth, minWidth), max(lineWidth, minWidth), abs(remainderX));
fragColor = mix(vec4(1.0, 0.0, 0.0, 1.0), vec4(0.8, 0.8, 0.8, 1.0), w);
参见 Three.js 示例,它使用了着色器:
(function onLoad() {
var camera, scene, renderer, orbitControls;
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 300);
camera.position.set(10, 15, -60);
loader = new THREE.TextureLoader();
loader.setCrossOrigin("");
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
scene.add(camera);
window.onresize = resize;
orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
var helper = new THREE.GridHelper(400, 10);
helper.material.opacity = 0.25;
helper.material.transparent = true;
scene.add(helper);
var axis = new THREE.AxesHelper(1000);
scene.add(axis);
var material = new THREE.ShaderMaterial({
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent,
});
material.extensions = {
derivatives: true
}
var geometry = new THREE.BoxGeometry( 100, 0.1, 100 );
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
function resize() {
var aspect = window.innerWidth / window.innerHeight;
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = aspect;
camera.updateProjectionMatrix();
}
function animate() {
requestAnimationFrame(animate);
orbitControls.update();
render();
}
function render() {
renderer.render(scene, camera);
}
})();
<script type='x-shader/x-vertex' id='vertex-shader'>
varying vec3 vertexPosition;
void main() {
vertexPosition = position.zyx;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;
varying vec3 vertexPosition;
int round(float x)
{
return int(x + 0.5 * sign(x));
}
void main(){
vec4 fragColor;
highp float lineWidth = 0.2;
highp float squareSize = 5.0;
highp int roundX = round(vertexPosition.x / squareSize);
highp float remainderX = vertexPosition.x - float(roundX)*squareSize;
vec2 dy = dFdy(vertexPosition.xy);
float minWidth = length(dy);
float w = smoothstep(min(lineWidth, minWidth), max(lineWidth, minWidth), abs(remainderX));
//float w = step(max(lineWidth, minWidth), abs(remainderX));
fragColor = mix(vec4(1.0, 0.0, 0.0, 1.0), vec4(0.8, 0.8, 0.8, 1.0), w);
gl_FragColor = fragColor;
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>