我试图在三个实现Cook-Torrance着色算法。我有一个主要工作的解决方案,但它没有显示环境光的影响。没有被光照射的立方体的侧面是完全黑色的。如果我删除" Beckmann术语"那我确实可以看到环境光效果:
同时,将Beckmann替换为始终返回0.0
的函数:
似乎错误行为的原因是分裂:
vec3 Specular = (Beckmann(NdotH) * G(NdotH, NdotV, VdotH, NdotL) * R_F(VdotH)) / ( NdotL* NdotV);
如果我将NdotL * NdotV
修改为NdotV
并将gl_FragColor
的计算更改为:
gl_FragColor = vec4(beta * NdotL * (1.0-s)*Kd + beta * s*Specular + ambient*Kd, 1.0);
一切似乎都能正常运作。
我不明白的是:为什么?在任何地方都没有提到这个划分的问题,而且我不能100%确定即使剩下的部门也不会在其他情况下引起问题。
这里是完整的MWE:
<html>
<head>
<title>Cook-Torrance BRDF computed by shader</title>
<style>
body {
font-family: Monospace;
background-color: #f0f0f0;
margin: 0px;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
</style>
<script src="lib/three.min.js"></script>
<script src="lib/OrbitControls.js"></script>
</head>
<body>
<script type="text/x-glsl" id="vertex">
varying vec3 transformedNormal;
varying vec3 pointPosition;
varying vec3 lightVector;
uniform vec3 pointLightPosition;
void main()
{
transformedNormal = normalMatrix * normal;
pointPosition = (modelViewMatrix * vec4( position, 1.0 )).xyz;
vec4 lPosition = viewMatrix * vec4( pointLightPosition, 1.0 );
lightVector = lPosition.xyz - pointPosition;
gl_Position = projectionMatrix * vec4(pointPosition,1.0);
}
</script>
<script type="text/x-glsl" id="ct-fragment">
uniform vec3 lightPower;
uniform vec3 ambient;
uniform vec3 Kd; // surface diffuse color
uniform vec3 Ks; // surface specular color: equal to R_F(0)
uniform float m; // material roughness (average slope of microfacets)
uniform float s; // percentage of incoming light which is specularly reflected
varying vec3 transformedNormal;
varying vec3 pointPosition;
varying vec3 lightVector;
#define PI 3.14159265
float G(float NdotH, float NdotV, float VdotH, float NdotL)
{
float G1 = 2.0 * NdotH * NdotV / VdotH;
float G2 = 2.0 * NdotH * NdotL / VdotH;
return min( 1.0, min( G1, G2 ));
}
vec3 R_F(float VdotH) {
return Ks + (1.0 - Ks)*pow(1.0-VdotH, 5.0);
}
float Beckmann(float NdotH){
float A = 1.0 / (pow(m,2.0)+pow(NdotH,4.0)*PI);
float B = exp( - pow( tan(acos(NdotH)) , 2.0) / pow(m,2.0));
return A*B;
}
void main()
{
vec3 n = normalize( transformedNormal );
vec3 v = normalize( -pointPosition );
vec3 l = normalize( lightVector );
vec3 h = normalize( v+l );
float NdotH = max(0.0, dot( n, h ));
float VdotH = max(0.0, dot( v, h ));
float NdotV = max(0.0, dot( n, v ));
float NdotL = max(0.0, dot( n, l ));
// specular BRDF
vec3 Specular = (Beckmann(NdotH) * G(NdotH, NdotV, VdotH, NdotL) * R_F(VdotH)) / ( NdotL* NdotV);
vec3 beta = lightPower / ( 4.0 * PI * pow( length(lightVector),2.0) );
gl_FragColor = vec4(beta * NdotL * ((1.0-s)*Kd + s*Specular) + ambient*Kd, 1.0);
}
</script>
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position = new THREE.Vector3(0,0,5);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xf0f0f0 );
document.body.appendChild( renderer.domElement );
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
var uniforms = {
Ks: { type: "v3", value: new THREE.Vector3() },
Kd: { type: "v3", value: new THREE.Vector3() },
ambient: { type: "v3", value: new THREE.Vector3() },
pointLightPosition: { type: "v3", value: new THREE.Vector3() },
lightPower: { type: "v3", value: new THREE.Vector3() },
s: {type: "f", value: 0},
m: {type: "f", value: 0}
};
var vs = document.getElementById("vertex").textContent;
var fs = document.getElementById("ct-fragment").textContent;
var material = new THREE.ShaderMaterial({ uniforms: uniforms, vertexShader: vs, fragmentShader: fs });
var geometry = new THREE.CubeGeometry(1, 1, 1);
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
light = new THREE.Mesh( new THREE.SphereGeometry( 1, 16, 16), new THREE.MeshBasicMaterial ({color: 0xffff00, wireframe:true}));
light.position = new THREE.Vector3( 10.0, 10.0, 10.0 );
scene.add( light );
uniforms.Ks.value = new THREE.Vector3( 0.95, 0.93, 0.88 );
uniforms.Kd.value = (new THREE.Vector3( 0.50754, 0.50754, 0.50754 ));
uniforms.ambient.value = (new THREE.Vector3( 0.5, 0.5, 0.5 ));
uniforms.pointLightPosition.value = new THREE.Vector3(light.position.x, light.position.y, light.position.z);
uniforms.lightPower.value = new THREE.Vector3( 7000.0, 7000.0, 7000.0 );
uniforms.s.value = 0.5;
uniforms.m.value = 0.1;
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
controls.update();
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
答案 0 :(得分:1)
阴影方程是Cook-Torrance着色模型的数学描述。 编写一个实际的着色器是一个不同的事情,应该考虑到浮点数之间的所有操作都不具有与等式中真实数学运算相同的属性。
在这种情况下,0
潜水会导致问题。事实上,问题是Specular
的定义是0
潜水,但在分配到gl_FragColor
时,我再次乘以NdotL
获得0 * inf = NaN
似乎NaN
被GPU解释为零/负数(因此显示为黑色)。
作为参考,正确的main()
是:
void main()
{
vec3 n = normalize( transformedNormal );
vec3 v = normalize( -pointPosition );
vec3 l = normalize( lightVector );
vec3 h = normalize( v+l );
vec3 specular = vec(0.0, 0.0, 0.0);
float NdotH = max(0.0, dot( n, h ));
float VdotH = max(0.0, dot( v, h ));
float NdotV = max(0.0, dot( n, v ));
float NdotL = max(0.0, dot( n, l ));
if (NdotL > 0 && NdotV > 0)
{
specular = (Beckmann(NdotH) * G(NdotH, NdotV, VdotH, NdotL) * R_F(VdotH)) / ( NdotL* NdotV);
}
vec3 beta = lightPower / ( 4.0 * PI * pow( length(lightVector),2.0) );
gl_FragColor = vec4(beta * NdotL * ((1.0-s)*Kd + s*specular) + ambient*Kd, 1.0);
}