<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>着色器Shader &#8211; 萧邦的博客</title>
	<atom:link href="https://www.zlhlab.top/tag/%e7%9d%80%e8%89%b2%e5%99%a8shader/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.zlhlab.top</link>
	<description>If not me who, If not now when</description>
	<lastBuildDate>Tue, 20 Feb 2018 12:38:46 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.6</generator>
	<item>
		<title>three.js 着色器学习笔记</title>
		<link>https://www.zlhlab.top/three-js-%e7%9d%80%e8%89%b2%e5%99%a8%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0/</link>
		
		<dc:creator><![CDATA[萧邦]]></dc:creator>
		<pubDate>Tue, 20 Feb 2018 12:14:39 +0000</pubDate>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[three.js]]></category>
		<category><![CDATA[着色器Shader]]></category>
		<guid isPermaLink="false">https://www.zlhlab.top/?p=527</guid>

					<description><![CDATA[着色器顾名思义就是给物体增加颜色的工具。我们也知道，创建物体时可以设置该物体的材质，材质决定了物体的外观。]]></description>
										<content:encoded><![CDATA[<p>着色器顾名思义就是给物体增加颜色的工具。我们也知道，创建物体时可以设置该物体的材质，材质决定了物体的外观。</p>
<p>其实使用着色器，就是给物体设置一种特殊的材质，这个材质是由着色器来决定显示效果。</p>
<p>下面以实际代码来展示不同材质的效果。</p>
<h4>基本材质</h4>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">function init () {
    // 创建渲染器
    const renderer = new THREE.WebGLRenderer({
        canvas: document.getElementById('main')
    })
    const width = window.innerWidth
    const height = window.innerHeight
    renderer.setClearColor(0xffffff)            
    renderer.setSize(width, height)

    // 创建场景
    const scene = new THREE.Scene()

    // 原点
    const origin = new THREE.Vector3(0, 0, 0)

    // 创建照相机
    const camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000)
    camera.position.set(0, 10, 10)
    camera.lookAt(origin)
    scene.add(camera)

    // 创建光源
    const light = new THREE.DirectionalLight(0xffffff, 0.8)
    light.position.set(0, 10, 10)
    scene.add(light)

    // 创建基本材质的球体
    const baseSphere = new THREE.Mesh(new THREE.SphereGeometry(1, 20, 20),
        new THREE.MeshBasicMaterial({
            color: 0xff0000
        })
    )
    scene.add(baseSphere)

    renderer.render(scene, camera)
}

init()
</code></pre>
<p>代码很简单，渲染出了一个基本材质<code>MeshBasicMaterial</code>的球体。</p>
<p><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2018/02/3531509-48b1d518e1050237.png" alt=""></p>
<h4>Lambert材质</h4>
<p>同样增加一个球体，其他参数相同仅仅是材质不同。</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">// 创建 Lambert 材质球体
const lambertSphere = new THREE.Mesh(new THREE.SphereGeometry(1, 20, 20),
                                     new THREE.MeshLambertMaterial({
    color: 0xff0000
})
                                    )
// 将该球体放置在基本材质球体的左边
lambertSphere.position.set(-3, 0, 0)
scene.add(lambertSphere)
</code></pre>
<p><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2018/02/3531509-4182a85d75f24ed8.png" alt=""></p>
<p>在基本材质的左边可以看到一个新的小球，这就是 Lambert 材质的小球。</p>
<h4>Phong材质</h4>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">// 创建 Phong材质 材质球体
const phongSphere = new THREE.Mesh(new THREE.SphereGeometry(1, 20, 20),
                                   new THREE.MeshPhongMaterial({
    color: 0xff0000
})
                                  )
phongSphere.position.set(3, 0, 0)
scene.add(phongSphere)
</code></pre>
<p><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2018/02/3531509-35a45747d6cd7d74.png" alt=""></p>
<p>除了上面三种外，还可以贴图。下面就开始进入正题，着色器，或者说着色器材质？</p>
<h4>着色器</h4>
<p>着色器分为顶点着色器与片元着色器，这里也不具体介绍，因为导师的书籍和博客都已经介绍得非常清楚了。</p>
<p>重点在这两句话：</p>
<ul>
<li>顶点着色器就是每个顶点调用一次的程序。在顶点着色器中，可以访问到顶点的三维位置、颜色、法向量等信息。可以通过修改这些值，或者将其传递到片元着色器中，实现特定的渲染效果。</li>
<li>片元着色器就是每个片元调用一次的程序。在片元着色器中，可以访问到片元在二维屏幕上的坐标、深度信息、颜色等信息。通过改变这些值，可以实现特定的渲染效果。</li>
</ul>
<p>OK，话不多说，再新建一个小球，这次先不设置材质，看看会怎么样。</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">// 没有材质的球体
const shaderSphere = new THREE.Mesh(new THREE.SphereGeometry(1, 20, 20))
shaderSphere.position.set(0, 3, 0)
scene.add(shaderSphere)
</code></pre>
<p><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2018/02/3531509-2db8b331f46f8d37.png" alt=""></p>
<p>而且刷新会有一个随机的颜色，因为似乎默认使用了基本材质？</p>
<p>使用基本材质（BasicMaterial）的物体，渲染后物体的颜色始终为该材质的颜色，而不会由于光照产生明暗、阴影效果。如果没有指定材质的颜色，则颜色是随机的。</p>
<p>接下来就来添加材质。</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">// 新建着色器材质
    var material = new THREE.ShaderMaterial({
        vertexShader: `
            uniform vec3 color;
            uniform vec3 light;

            varying vec3 vColor;
            varying vec3 vNormal;
            varying vec3 vLight;

            void main()
            {
                // pass to fs
                vColor = color;
                vNormal = normalize(normalMatrix * normal);

                vec4 viewLight = viewMatrix * vec4(light, 1.0);
                vLight = viewLight.xyz;

                gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
            }`,
        fragmentShader: `
            varying vec3 vColor;
            varying vec3 vNormal;
            varying vec3 vLight;

            void main() {
                float diffuse = dot(normalize(vLight), vNormal);
                if (diffuse &gt; 0.8) {
                    diffuse = 1.0;
                }
                else if (diffuse &gt; 0.5) {
                    diffuse = 0.6;
                }
                else if (diffuse &gt; 0.2) {
                    diffuse = 0.4;
                }
                else {
                    diffuse = 0.2;
                }

                gl_FragColor = vec4(vColor * diffuse, 1.0);
            }`,
        uniforms: {
            color: { 
                type: 'v3', // 指定变量类型为三维向量
                value: new THREE.Color('#ff0000') // 要传递给着色器的颜色值
            },
            light: {       
                type: 'v3',
                value: light.position // 光源位置
            }
        }
    })
</code></pre>
<p>这样就新建了一个材质，并且该材质的显示效果由<code>vertexShader</code>和<code>fragmentShader</code>两个着色器决定。</p>
<p>将该材质用在我们没有材质的小球上。</p>
<pre class="prism-highlight line-numbers" data-start="1"><code class="language-null">shaderSphere.material = material
</code></pre>
<p>再看看浏览器，发现和之前完全不同了。</p>
<p><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2018/02/3531509-b70fcd846d303b0b.png" alt=""></p>
<p>可以看到上面的球体颜色有很明显的变化，上面颜色浅下面深，因为光是从顶上照射下来，所以有这种效果，如果改变光照方向，效果也不一样。</p>
<p>实现了需要的效果没有太大意义，更需要的是理解为什么会有这种效果。</p>
<h4>顶点着色器</h4>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">uniform vec3 color;
uniform vec3 light;

varying vec3 vColor;
varying vec3 vNormal;
varying vec3 vLight;

void main() {
    // pass to fs
    vColor = color;
    vNormal = normalize(normalMatrix * normal);

    vec4 viewLight = viewMatrix * vec4(light, 1.0);
    vLight = viewLight.xyz;

    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</code></pre>
<p>一开始看的时候也完全不明白写的是什么，以学习另一种语言的心理再来看就慢慢能够理解了。</p>
<p><code>uniform</code>和<code>varying</code>就是和 js 中的 var、let一样，用来声明变量的。所以这段代码一开始就声明了五个变量</p>
<ul>
<li>color</li>
<li>light</li>
<li>vColor</li>
<li>vNormal</li>
<li>vLight</li>
</ul>
<p>使用<code>uniform</code>声明的变量将会从我们的 js 代码中传递过来，所以<code>color</code>就是<code>#ff0000</code>，light就是光源位置。</p>
<p>然后是<code>main()</code>函数，表示会执行这里面的代码，第一行<code>vColor = color;</code>很简单就是一个赋值操作。<br />
接下来就复杂了。。。</p>
<pre class="prism-highlight line-numbers" data-start="1"><code class="language-null">vNormal = normalize(normalMatrix * normal);
</code></pre>
<p>虽然能知道是给<code>vNormal</code>变量赋值，但是<code>normalize</code>、<code>normalMatrix</code>和<code>normal</code>都是哪里来的？</p>
<p>回头看看之前说的重点，顶点着色器，也就是这段代码能够访问到顶点的三维位置、颜色、法向量等信息。<code>normalMatrix</code>和<code>normal</code>就是顶点信息，而<code>normalize</code>则是全局方法。</p>
<p>OK，现在知道变量是怎么来的，但是为啥要这么做呢？。。。。<br />
竟无语凝噎，还是看导师的博客吧。</p>
<ul>
<li>算法理解</li>
</ul>
<p>总之，就是得到了 <code>vNormal</code>、<code>vColor</code>和<code>vLight</code>这三个变量并传递给片元着色器。</p>
<h4>片元着色器</h4>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">varying vec3 vColor;
varying vec3 vNormal;
varying vec3 vLight;

void main() {
    float diffuse = dot(normalize(vLight), vNormal);
    if (diffuse &gt; 0.8) {
        diffuse = 1.0;
    }
    else if (diffuse &gt; 0.5) {
        diffuse = 0.6;
    }
    else if (diffuse &gt; 0.2) {
        diffuse = 0.4;
    }
    else {
        diffuse = 0.2;
    }

    gl_FragColor = vec4(vColor * diffuse, 1.0);
}
</code></pre>
<p>最为核心的代码就是<code>main()</code>函数中的第一句：</p>
<pre class="prism-highlight line-numbers" data-start="1"><code class="language-null">float diffuse = dot(normalize(vLight), vNormal);
</code></pre>
<p>调用<code>normalize</code>方法得到的结果，与<code>vNormal</code>作为参数再传入dot方法，最后得到的结果是片元的亮度值。</p>
<p>并且，将亮度值固定为<code>1.0</code>、<code>0.6</code>、<code>0.4</code>和<code>0.2</code>这四个值，所以，最终渲染出来的结果肯定只有四种颜色。</p>
<p>如果不将亮度值规定为四个值，而是直接使用，又是什么效果呢？</p>
<p><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2018/02/3531509-b70fcd846d303b0b.png" alt=""></p>
<p>结果和预想的一样，diffuse是一个0 ~ 1的值，并且是逐渐变化的。</p>
<h4>总结</h4>
<p>物体的外观是由材质决定的，three.js 中有提供现成的材质，也可以自己使用着色器来自定义材质。</p>
<p>着色器分为顶点着色器与片元着色器，这两种着色器其实本质上就是以另一种语言实现的函数方法。由于是另一种语言所以也是有自己的变量声明规则以及全局方法，同时 three.js 也能够传递变量给着色器。</p>
<p>重点在于着色器中能够直接取到关于物体的顶点信息与片元信息，正是借助这些信息才能够定制显示效果。</p>
<h4>参考</h4>
<ul>
<li><a href="http://zhangwenli.com/blog/2017/03/05/cartoon-shading-1/" title="卡通渲染（上）" target="_blank">卡通渲染（上）</a></li>
</ul>
<p>本文仅作webgl学习技术参考，点击<a href="http://ife.baidu.com/note/detail/id/928" title="阅读原文" target="_blank">阅读原文</a>。</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
