着色器-简单的SSS照明问题 [英] Shader - Simple SSS lighting issue

查看:106
本文介绍了着色器-简单的SSS照明问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用着色器创建一个简单的地下散射效果,但是我面临一个小问题.

I am trying to create a simple subsurface scattering effect using a shader but I am facing a small issue.

查看这些屏幕截图.这三幅图像代表三种照明状态(表面上,真正接近表面,子表面),具有各种照明颜色(红色和蓝色),并且始终具有相同的子表面颜色(红色).

Look at those screenshots. The three images represents three lighting states (above surface, really close to surface, subsurface) with various lighting colors (red and blue) and always the same subsurface color (red).

您可能会注意到,当光线在表面上方并且确实靠近该表面时,它的影响似乎最小化了预期的行为.但是问题在于,对于次表面部分来说,行为是相同的,根据我的着色器代码,这是正常现象,但我认为当接近表面时,次表面光的影响应该更高.我建议您看一下屏幕快照以获得预期的结果.

As you might notice when the light is above the surface and really close to this surface its influence appears to minimize which is the expected behavior. But the problem is that is behaves the same for the subsurface part, this is normal according to my shader code but in my opinion the subsurface light influence should be higher when going close to the surface. I suggest you to look at the screenshot for the expected result.

我该怎么做?

这是简化的着色器代码.

Here is the simplified shader code.

half ndotl = max(0.0f, dot(normalWorld, lightDir));
half inversendotl = max(0.0f, dot(normalWorld, -lightDir));
half3 lightColor = _LightColor0.rgb * ndotl; // This is the normal light color calculation
half3 subsurfacecolor = translucencyColor.rgb * inversendotl; // This is the subsurface color
half3 topsubsurfacecolor = translucencyColor.rgb; // This is used for adding subsurface color to top surface
final = subsurfacescolor + lerp(lightColor, topsubsurfacecolor * 0.5, 1 - ndotl - inversendotl);

推荐答案

您如何实现地下散射效果的方法非常粗糙.使用如此简单的方法很难获得良好的结果. 坚持选择的方法,我向您推荐以下内容:

The way, how you have implemented subsurface scattering effect is very rough. It is hard to achieve nice result using so simple approach. Staying within selected approach, I would recommend you the following things:

  • 根据平方反比定律考虑到光源的距离.这适用于直射光和地下两个分量.

  • Take into account distance to the light source accordingly to the inverse square law. This applies to both components, direct light and subsurface.

一旦光线位于表面之后,最好忽略内部法线和光线方向的点积,因为您永远不知道光线将如何穿过对象.另一个原因是,由于折射定律(假设物体的折射系数高于空气的折射系数)使得该点积的影响较小.您可以只使用步骤函数用于在光源位于表面后方打开次表面组件.

Once the light is behind the surface, it is better to ignore the dot product of the inner normal and direction to the light, because you never know how the light would travel through the object. One more reason is that because of the law of refraction (assuming that refraction coefficient of the object is higher than one of the air) makes this dot product less influential. You may just use a step function to turn on subsurface component once the light source is behind the surface.

因此,着色器的修改版本如下:

So, the modified version of your shader would be as follows:

half3 toLightVector = u_lightPos - v_fragmentPos;
half lightDistanceSQ = dot(toLightVector, toLightVector);
half3 lightDir = normalize(toLightVector);
half ndotl = max(0.0, dot(v_normal, lightDir));
half inversendotl = step(0.0, dot(v_normal, -lightDir));
half3 lightColor = _LightColor0.rgb * ndotl / lightDistanceSQ  * _LightIntensity0; 
half3 subsurfacecolor = translucencyColor.rgb * inversendotl / lightDistanceSQ  * _LightIntensity0;
half3 final = subsurfacecolor + lightColor;

u_lightPos -包含光源位置的制服, v_fragmentPos -包含片段位置的变量.

Where u_lightPos - uniform that contains position of the light source, v_fragmentPos - varying that contains position of the fragment.

以下是使用 three.js 的glsl中的示例:

Here is an example in glsl using three.js:

   var container;
   var camera, scene, renderer;
   var sssMesh;
   var lightSourceMesh;
   var sssUniforms;

   var clock = new THREE.Clock();

   init();
   animate();

   function init() {
     container = document.getElementById('container');

     camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 3000);
     camera.position.z = 4;
     camera.position.y = 2;
     camera.rotation.x = -0.45;

     scene = new THREE.Scene();

     var boxGeometry = new THREE.CubeGeometry(0.75, 0.75, 0.75);

     var lightSourceGeometry = new THREE.CubeGeometry(0.1, 0.1, 0.1);

     sssUniforms = {
       u_lightPos: {
         type: "v3",
         value: new THREE.Vector3()
       }
     };

     var sssMaterial = new THREE.ShaderMaterial({
       uniforms: sssUniforms,
       vertexShader: document.getElementById('vertexShader').textContent,
       fragmentShader: document.getElementById('fragment_shader').textContent
     });

     var lightSourceMaterial = new THREE.MeshBasicMaterial();

     sssMesh = new THREE.Mesh(boxGeometry, sssMaterial);
     sssMesh.position.x = 0;
     sssMesh.position.y = 0;
     scene.add(sssMesh);

     lightSourceMesh = new THREE.Mesh(lightSourceGeometry, lightSourceMaterial);
     lightSourceMesh.position.x = 0;
     lightSourceMesh.position.y = 0;
     scene.add(lightSourceMesh);

     renderer = new THREE.WebGLRenderer();
     container.appendChild(renderer.domElement);

     onWindowResize();

     window.addEventListener('resize', onWindowResize, false);

   }

   function onWindowResize(event) {
     camera.aspect = window.innerWidth / window.innerHeight;
     camera.updateProjectionMatrix();
     renderer.setSize(window.innerWidth, window.innerHeight);
   }

   function animate() {
     requestAnimationFrame(animate);
     render();
   }

   function render() {
     var delta = clock.getDelta();
     var lightHeight = Math.sin(clock.elapsedTime * 1.0) * 0.5 + 0.7;
     lightSourceMesh.position.y = lightHeight;
     sssUniforms.u_lightPos.value.y = lightHeight;
     sssMesh.rotation.y += delta * 0.5;
     renderer.render(scene, camera);
   }

    body {

      color: #ffffff;

      background-color: #050505;

      margin: 0px;

      overflow: hidden;

    }

    <script src="http://threejs.org/build/three.min.js"></script>
    <div id="container"></div>

    <script id="fragment_shader" type="x-shader/x-fragment">

    varying vec3 v_fragmentPos;
    varying vec3 v_normal;
    uniform vec3 u_lightPos;
    void main(void)
    {
        vec3 _LightColor0 = vec3(1.0,0.5,0.5);  
        float _LightIntensity0 = 0.2;
        vec3 translucencyColor = vec3(0.8,0.2,0.2);
        vec3 toLightVector = u_lightPos - v_fragmentPos;
        float lightDistanceSQ = dot(toLightVector, toLightVector);
        vec3 lightDir = normalize(toLightVector);
    	float ndotl = max(0.0, dot(v_normal, lightDir));
    	float inversendotl = step(0.0, dot(v_normal, -lightDir));
        vec3 lightColor = _LightColor0.rgb * ndotl / lightDistanceSQ * _LightIntensity0; 
    	vec3 subsurfacecolor = translucencyColor.rgb * inversendotl / lightDistanceSQ * _LightIntensity0;
    	vec3 final = subsurfacecolor + lightColor;
    	gl_FragColor=vec4(final,1.0);
    }

    </script>

    <script id="vertexShader" type="x-shader/x-vertex">

    varying vec3 v_fragmentPos;
    varying vec3 v_normal;
                
    void main()
    {
    	vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
        v_fragmentPos =  (modelMatrix * vec4( position, 1.0 )).xyz;
        v_normal =  (modelMatrix * vec4( normal, 0.0 )).xyz;
    	gl_Position = projectionMatrix * mvPosition;
    }

    </script>

有许多不同的SSS仿真技术. 纹理空间扩散基于阴影贴图的半透明是最常用的技术.

There are large amount of different techniques of simulation of SSS. Texture-space diffusion and shadowmap-based translucency are the most frequently used techniques.

查看GPU Gems上的这篇文章,其中介绍了提到的技术. 此外,您还可以找到有趣的

Check this article from GPU Gems, it describes mentioned techniques. Also, you can find interesting this presentation from EA. It mentions approach that is very close to yours for rendering plants.

球谐函数也适用于静态几何体,但是这种方法非常复杂,需要预先计算的辐照度传递.查看文章,该文章显示了使用球谐函数近似SSS

Spherical harmonics also works well for static geometry, but this approach is very complicated and it needs precomputed irradiance transfer. Check this article, that shows use of spherical harmonics to approximate SSS.

这篇关于着色器-简单的SSS照明问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆