2015-12-30 49 views
5

Mam ustawiony ten minimalny przypadek testowy, który można łatwo zobaczyć wzory mory wytwarzane przez podkreślenie oscylujących czerwony kolor za pomocą niestandardowego modułu cieniującego fragmentu (jsfiddle).Usuwanie wzorów mory produkowanych przez shadery GLSL

Jaka jest ogólna technika usuwania takich wzorów za pomocą GLSL? Zakładam, że obejmuje to rozszerzenie derywatów, ale nigdy nie do końca rozumiałem, jak go wdrożyć. Zasadniczo muszę się przeciwdziałać wygładzaniu, tak myślę?

var canvas = document.getElementById('canvas'); 
 
var scene = new THREE.Scene(); 
 
var renderer = new THREE.WebGLRenderer({canvas: canvas, antialias: true}); 
 
var camera = new THREE.PerspectiveCamera(75, canvas.clientWidth/canvas.clientWidth, 1, 1000); 
 

 
var geometry = new THREE.SphereGeometry(50, 50, 50); 
 
var material = new THREE.ShaderMaterial({ 
 
    vertexShader: document.getElementById('vertex-shader').textContent, 
 
    fragmentShader: document.getElementById('fragment-shader').textContent 
 
}); 
 
var sphere = new THREE.Mesh(geometry, material); 
 

 
scene.add(sphere); 
 

 
camera.position.z = 100; 
 

 
var period = 30; 
 
var clock = new THREE.Clock(); 
 
render(); 
 

 
function render() { 
 
    requestAnimationFrame(render); 
 
    
 
    if (canvas.width !== canvas.clientWidth || canvas.height !== canvas.clientHeight) { 
 
    renderer.setSize(canvas.clientWidth, canvas.clientHeight, false); 
 
    camera.aspect = canvas.clientWidth/canvas.clientHeight; 
 
    camera.updateProjectionMatrix(); 
 
    } 
 
    
 
    sphere.rotation.y -= clock.getDelta() * 2 * Math.PI/period; 
 
    renderer.render(scene, camera); 
 
}
html, body, #canvas { 
 
    margin: 0; 
 
    padding: 0; 
 
    width: 100%; 
 
    height: 100%; 
 
    overflow: hidden; 
 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"></script> 
 
<canvas id="canvas"></canvas> 
 
<script id="vertex-shader" type="x-shader/x-vertex"> 
 
    varying vec2 vUv; 
 

 
    void main() { 
 
    vUv = uv; 
 
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 
 
    } 
 
</script> 
 
<script id="fragment-shader" type="x-shader/x-fragment"> 
 
    #define M_TAU 6.2831853071795864769252867665590 
 

 
    varying vec2 vUv; 
 

 
    void main() { 
 
    float w = sin(500.0 * M_TAU * vUv.x)/2.0 + 0.5; 
 
    vec3 color = vec3(w, 0.0, 0.0); 
 
    gl_FragColor = vec4(color, 1.0); 
 
    } 
 
</script>

Aktualizacja: Starałem się wdrożyć super-sampling, nie wiem, czy ja wdrożyły go poprawnie, ale nie wydaje się, aby pomóc zbyt dużo.

+0

@ WacławJasper Używanie coord UV prostu łatwym sposobem na uzyskanie tego efektu. Sam szew nie stwarza problemu, w przeciwnym razie pojawiłby się tylko problem wokół samego szwu. –

Odpowiedz

6

Niestety, wzór mory tutaj jest wynikiem linii o wysokim kontraście zbliżających się do Nyquist Frequency. Innymi słowy, nie ma dobrego sposobu, aby linia o wysokim kontraście o szerokości 1- lub 2-pikselowej płynnie przechodziła do następnego piksela, bez wprowadzania takich artefaktów lub zamazywania linii, aby były nie do odróżnienia.

Wspomniał Pan o rozszerzeniu zakresu instrumentów pochodnych i rzeczywiście można go wykorzystać do określenia, jak szybko zmieniają się promienie UV w przestrzeni ekranu, a tym samym do określenia, jakie rozmycie jest potrzebne, aby zmieść ten problem pod dywan. W zmodyfikowanej wersji własnego przykładu poniżej, próbuję użyć fwidth, aby zmienić sferę na czerwoną, gdzie hałas jest zły. Spróbuj grać z niektórymi zmiennymi zdefiniowanymi tutaj jako stałe, zobacz, co możesz znaleźć.

var canvas = document.getElementById('canvas'); 
 
var scene = new THREE.Scene(); 
 
var renderer = new THREE.WebGLRenderer({canvas: canvas, antialias: true}); 
 
var camera = new THREE.PerspectiveCamera(75, canvas.clientWidth/canvas.clientWidth, 1, 1000); 
 

 
var geometry = new THREE.SphereGeometry(50, 50, 50); 
 
var material = new THREE.ShaderMaterial({ 
 
    vertexShader: document.getElementById('vertex-shader').textContent, 
 
    fragmentShader: document.getElementById('fragment-shader').textContent 
 
}); 
 
var sphere = new THREE.Mesh(geometry, material); 
 

 
scene.add(sphere); 
 

 
camera.position.z = 100; 
 

 
var period = 30; 
 
var clock = new THREE.Clock(); 
 
render(); 
 

 
function render() { 
 
    requestAnimationFrame(render); 
 
    
 
    if (canvas.width !== canvas.clientWidth || canvas.height !== canvas.clientHeight) { 
 
    renderer.setSize(canvas.clientWidth, canvas.clientHeight, false); 
 
    camera.aspect = canvas.clientWidth/canvas.clientHeight; 
 
    camera.updateProjectionMatrix(); 
 
    } 
 
    
 
    sphere.rotation.y -= clock.getDelta() * 2 * Math.PI/period; 
 
    renderer.render(scene, camera); 
 
}
html, body, #canvas { 
 
    margin: 0; 
 
    padding: 0; 
 
    width: 100%; 
 
    height: 100%; 
 
    overflow: hidden; 
 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"></script> 
 
<canvas id="canvas"></canvas> 
 
<script id="vertex-shader" type="x-shader/x-vertex"> 
 
    varying vec2 vUv; 
 

 
    void main() { 
 
    vUv = uv; 
 
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 
 
    } 
 
</script> 
 
<script id="fragment-shader" type="x-shader/x-fragment"> 
 
    #extension GL_OES_standard_derivatives : enable 
 

 
    #define M_TAU 6.2831853071795864769252867665590 
 

 
    varying vec2 vUv; 
 

 
    void main() { 
 
    float linecount = 200.0; 
 
    float thickness = 0.0; 
 
    float blendregion = 2.8; 
 
    
 
    // Loosely based on https://github.com/AnalyticalGraphicsInc/cesium/blob/1.16/Source/Shaders/Materials/GridMaterial.glsl#L17-L34 
 
    float scaledWidth = fract(linecount * vUv.s); 
 
    scaledWidth = abs(scaledWidth - floor(scaledWidth + 0.5)); 
 
    vec2 dF = fwidth(vUv) * linecount; 
 
    float value = 1.0 - smoothstep(dF.s * thickness, dF.s * (thickness + blendregion), scaledWidth); 
 
    gl_FragColor = vec4(value, 0.0, 0.0, 1.0); 
 
    } 
 
</script>

+0

Dzięki za odpowiedź! Mogę tylko poprosić o dalsze informacje lub dokumentację różnych technik tego typu rzeczy? Czy w zasadzie po prostu rozmazują rzeczy o wysokiej częstotliwości? –

+1

O ile mi wiadomo, to jedyne rozwiązanie: wyświetlacze oparte na pikselach po prostu nie mogą pokazywać częstotliwości wyższych niż gęstość pikseli. W powyższym kodzie, 'grubość' i' blendregion' są określone w pikselach ekranu za pomocą 'fwidth', co oznacza, że ​​linie zaczynają zachodzić na siebie w odstępach mniejszych niż ~ 3 piksele. Mniej niż to, a masz tylko 1 piksel dla linii i 1 piksel dla luki, a rzeczy stają się kłopotliwe, gdy linie nie przyklejają się do siatki pikseli. – emackey

+1

BTW, logika, na której oparty jest ten kod, jest pełniej opisana w książce "Projektowanie silników 3D dla wirtualnych globów" autorstwa Cozzi i Ring, listing 4.13. – emackey