着色器测试网格 - 线条呈锯齿状且断裂
Shader testgrid - lines jagged and breaking
我需要为测试网格地面编写一个简单的着色器。我想基本上在着色器代码中绘制平行线。
问题:随着线条与相机的距离越来越远,它们开始分裂并且出现缝隙。我明白为什么我的代码会发生这种情况 - 因为 OpenGL 将片段的位置近似为离我计算的点太远,所以它将它标记为不属于一条线。
我正在将平面矢量的实际世界位置传递到我的着色器 - 这就是我计算它的方式。
我已经玩了一个小时的算法,但似乎无法得到好的结果。
我尝试过的最好的想法是包含一个小系数,该系数随着线离相机的距离越来越远而增加 - 但结果并不令人满意。我线性地计算了系数,但我想我需要一些更聪明的公式来走这条路,因为屏幕上的线条变细的速度不是线性的。到目前为止我还想不通。目前它要么使近线太粗,这是不可取的,要么对于远线仍然有同样的问题。
为简化起见,我目前只绘制 X 轴线
我附上了一段着色器代码和问题的屏幕截图。
#version 300 es
precision highp float;
precision highp int;
in highp vec3 vertexPosition;
out mediump vec4 fragColor;
void main()
{
highp float lineWidth = 0.2;
highp float squareSize = 5.0f;
highp int roundX = int(vertexPosition.x / squareSize);
highp int roundY = int(vertexPosition.z / squareSize);
highp float remainderX = vertexPosition.x - float(roundX)*squareSize;
highp float remainderY = vertexPosition.x - float(roundY)*squareSize;
// this is the small coefficient I was trying to add to linewidth
highp float test = abs(0.08 * float(roundX));
if (abs(remainderX) <= (lineWidth))
{
fragColor = vec4(1,0,0, 1);
}
else
{
fragColor = vec4(0.8,0.8,0.8, 1);
}
}
第一个答案修复了换行的主要问题,但引入了视觉错误。会去尝试找出原因。不管怎样,这已经是个好主意了!但是正如您所看到的那样,线在最后变得更宽。
编辑:找到了。在执行 dFdy
之前,刚刚从 vertexPosition
中删除了 Z 坐标。现在我只需要一种方法来使线条更平滑而不是像楼梯一样。
p.s。不要看代码的优化程度 - 我目前只是在寻找正确的想法
p.p.s。如果有人能告诉我如何为这个例子做简单的抗锯齿 - 这也是最受欢迎的。
重要的是 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 个片段之间的距离 space。因此可以定义一条线的最小粗细:
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>
我需要为测试网格地面编写一个简单的着色器。我想基本上在着色器代码中绘制平行线。
问题:随着线条与相机的距离越来越远,它们开始分裂并且出现缝隙。我明白为什么我的代码会发生这种情况 - 因为 OpenGL 将片段的位置近似为离我计算的点太远,所以它将它标记为不属于一条线。
我正在将平面矢量的实际世界位置传递到我的着色器 - 这就是我计算它的方式。
我已经玩了一个小时的算法,但似乎无法得到好的结果。
我尝试过的最好的想法是包含一个小系数,该系数随着线离相机的距离越来越远而增加 - 但结果并不令人满意。我线性地计算了系数,但我想我需要一些更聪明的公式来走这条路,因为屏幕上的线条变细的速度不是线性的。到目前为止我还想不通。目前它要么使近线太粗,这是不可取的,要么对于远线仍然有同样的问题。
为简化起见,我目前只绘制 X 轴线
我附上了一段着色器代码和问题的屏幕截图。
#version 300 es
precision highp float;
precision highp int;
in highp vec3 vertexPosition;
out mediump vec4 fragColor;
void main()
{
highp float lineWidth = 0.2;
highp float squareSize = 5.0f;
highp int roundX = int(vertexPosition.x / squareSize);
highp int roundY = int(vertexPosition.z / squareSize);
highp float remainderX = vertexPosition.x - float(roundX)*squareSize;
highp float remainderY = vertexPosition.x - float(roundY)*squareSize;
// this is the small coefficient I was trying to add to linewidth
highp float test = abs(0.08 * float(roundX));
if (abs(remainderX) <= (lineWidth))
{
fragColor = vec4(1,0,0, 1);
}
else
{
fragColor = vec4(0.8,0.8,0.8, 1);
}
}
第一个答案修复了换行的主要问题,但引入了视觉错误。会去尝试找出原因。不管怎样,这已经是个好主意了!但是正如您所看到的那样,线在最后变得更宽。
编辑:找到了。在执行 dFdy
之前,刚刚从 vertexPosition
中删除了 Z 坐标。现在我只需要一种方法来使线条更平滑而不是像楼梯一样。
p.s。不要看代码的优化程度 - 我目前只是在寻找正确的想法 p.p.s。如果有人能告诉我如何为这个例子做简单的抗锯齿 - 这也是最受欢迎的。
重要的是 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 个片段之间的距离 space。因此可以定义一条线的最小粗细:
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>