thi-ng / ws-ldn-11

High Performance ClojureScript - WebGL, WebRTC, Web workers & asm.js
http://workshop.thi.ng
Apache License 2.0
10 stars 3 forks source link

Flat shading look #2

Closed Ninerian closed 4 years ago

Ninerian commented 7 years ago

Hello,

I actually dig into your geom library and learn webgl on this way. So I'm a newbie at both fields. As you open sourced the results of your workshops, I played a little bit around and got some nice results. But what I can't get rid of, is the flat shaded look of the models. I tried different shaders but neither results in a smooth shading of the models. So I created a little test case to explain the problem.

In this scene I creaete a sphere and give it a color. I also set a light and a camera. The result of using the given lamber shader is this:

lamber shader

With the vertex lighting I got this:

vertex-lightning

And with fragment lighting this:

fragment-lightning

Could you explain to me, why the polygons are always flat shaded and how I could achieve a smooth look? Thank you very much in advance.

Following you find the listing of my example.

(ns webgl-lesson.core
    (:require [thi.ng.math.core :as m :refer [PI HALF_PI TWO_PI]]
                        [thi.ng.geom.gl.core :as gl]
                        [thi.ng.geom.gl.webgl.constants :as glc]
                        [thi.ng.geom.gl.webgl.animator :as anim]
                        [thi.ng.geom.gl.glmesh :as glm]
                        [thi.ng.geom.gl.camera :as cam]
                        [thi.ng.geom.gl.shaders :as sh]
                        [thi.ng.geom.gl.shaders.basic :as basic]
                        [thi.ng.geom.gl.shaders.lambert :as lambert]
                        [thi.ng.geom.core :as g]
                        [thi.ng.geom.vector :as v :refer [vec2 vec3]]
                        [thi.ng.geom.matrix :as mat :refer [M44]]
                        [thi.ng.geom.sphere :as s]
                        [thi.ng.geom.quad :as q]
                        [thi.ng.geom.attribs :as attr]
                        [thi.ng.color.core :as col]))

(enable-console-print!)

(def fragment-lighting-shader-spec
    {:vs       "void main() {
                                    vPosition = vec3(view * model * position);
                                    vNormal = vec3(view * model * vec4(normal, 0.0));
                  vColor = color;
                                    gl_Position = proj * view * model * position;
    }"
     :fs       "void main() {
                float distance = length(lightPos - vPosition);
                vec3 lightVector = normalize(lightPos - vPosition);

                float diffuse = max(dot(vNormal, lightVector), 0.0);

                diffuse = diffuse * (1.0 / ( 1.0 + (0.25 * distance * distance)));

                                vec4 fragmentColor = vec4(0.0, 1.0, 1.0, 1.0);
                                gl_FragColor = vColor * diffuse;
                            }"
     :uniforms {:model    [:mat4 M44]
                            :view     :mat4
                            :proj     :mat4
                            :lightPos :vec3}

     :attribs  {
                            :position :vec4
                            :normal   :vec3
                            :color    :vec4}

     :varying  {:vPosition :vec3
                            :vColor    :vec4
                            :vNormal   :vec3}
     :state    {:depth-test true}})

(def vertex-lighting-shader-spec
    {:vs       "void main() {
                                    vPosition = vec3(view * model * position);
                                    vNormal = vec3(view * model * vec4(normal, 0.0));

                                    float distance = length(lightPos - vPosition);

                                    vec3 lightVector = normalize(lightPos - vPosition);

                                    float diffuse = max(dot(vNormal, lightVector), 0.0);
                                    diffuse = diffuse * (1.0 / ( 1.0 + (0.25 * distance * distance)));

                                    vColor = color;
                                    gl_Position = proj * view * model * position;
                         }"
     :fs       "void main() {
                                gl_FragColor = vColor;
                            }"
     :uniforms {:model    [:mat4 M44]
                            :view     :mat4
                            :proj     :mat4
                            :lightPos :vec3}

     :attribs  {
                            :position :vec4
                            :normal   :vec3
                            :color    :vec4}

     :varying  {:vPosition :vec3
                            :vColor    :vec4
                            :vNormal   :vec3}
     :state    {:depth-test true}})

(defn main
    [& args]
    (let [gl (gl/gl-context "main")
                view-rect (gl/get-viewport-rect gl)
                lambert-shader (sh/make-shader-from-spec gl lambert/shader-spec-attrib)
                sphere (-> (s/sphere 1)
                                     (g/center)
                                     (g/as-mesh {:mesh    (glm/gl-mesh 4096 #{:col :fnorm})
                                                             :res     40
                                                             :attribs {:col (fn [_ _ v _] (col/rgba 0.5 0 0.5))}})
                                     (gl/as-gl-buffer-spec {})
                                     (cam/apply
                                         (cam/perspective-camera
                                             {:eye    (vec3 0 0 1.5)
                                                :fov    90
                                                :aspect view-rect}))
                                     (update :uniforms merge
                                                     {:lightPos (vec3 1.3 0 0)})
                                     ; (assoc :shader lambert-shader)
                                     (assoc :shader (sh/make-shader-from-spec gl fragment-lighting-shader-spec))
                                     ; (assoc :shader (sh/make-shader-from-spec gl vertex-lighting-shader-spec))
                                     (gl/make-buffers-in-spec gl glc/static-draw))]

        (anim/animate
            (fn [t frame]
                (doto gl
                    (gl/set-viewport view-rect)
                    (gl/clear-color-and-depth-buffer 0 0 0.05 1 1)
                    (gl/draw-with-shader
                        (assoc-in sphere [:uniforms :model]
                                            (-> M44 (g/rotate-x (m/radians 24.5)) (g/rotate-y 3))))))
            true)))

(main)
postspectacular commented 7 years ago

Hi @Ninerian - glad to hear you find it helpful & use it for learning webgl... the flat shaded look is because the thi.ng.geom.gl.GLMesh type only computes face normals (the :fnorm attribute) automatically, but in order to get smooth shading you'll need to compute individual normals for each vertex (:vnorm attrib), rather than just 1 normal per face...

However, due to the nature the data is stored, these vnormals can not be easily calculated by the GLMesh and will therefore have to be either first computed via a different mesh type (i.e. thi.ng.geom.gmesh ns => graph mesh) or via an attrib generator fn. For spheres, it's best/fastest to use an attrib generator directly, since the normal for each vertex simply is its normalized position (provided the sphere is around [0,0,0]):

(-> (s/sphere 1)
    (g/as-mesh
     {:mesh    (glm/gl-mesh 4096 #{:vnorm})
      :attribs {:vnorm (fn [_ _ v _] (m/normalize v))}
      :res     24}))

Currently, gmesh will be easiest to use for other mesh types, since it supports vertex normal calculation, but also is slower and doesn't support other attributes (like color, uv coords) - this is because this mesh type is more meant for digital fabrication or other non-display oriented use cases.

There's yet another mesh type thi.ng.geom.indexedmesh which is supposed to be a hybrid between gl-mesh & gmesh, but work is still incomplete and that too so far only supports face normals - sorry! Only that many hours in a day... :)

Hope that helps, though!

postspectacular commented 7 years ago

@Ninerian I've just updated the globe demo (https://github.com/thi-ng/ws-ldn-11/blob/master/day1/ex03/src/ex03/webgl07.cljs) to use vertex normals & lambert lighting, so you can see it all in situ...

Ninerian commented 7 years ago

@postspectacular thank you for the explanation and the change of the example. How would you position you're library against ThreeJS? Is it comparable in speed und functionality?

postspectacular commented 7 years ago

@Ninerian pleasure! re: threejs - there're several differences (not all of which might be interesting to you):

Can't comment much on speed comparisons, really depends on what you want to do, but I went out of my way to optimize as much as I could without sacrificing other usability aspects (and due to OpenGL vs WebGL differences, I've got twice the maintenance effort). Unless you want to do constant CPU bound mesh updates, the main performance depends on your draw call scheduling and shader complexity. There shouldn't be any real difference to three.js.

Functionality wise: This is largely a one man effort to bring a more clojuresque approach to working with geometry, but I won't and I have zero intention to compete with the humungous threejs community. Also, please bear in mind, the GL parts of thi.ng/geom are just a small(ish), albeit exciting, aspect of the whole project... (see list of examples & features on https://github.com/thi-ng/geom/tree/develop) Also, combine thi.ng/geom with Reagent & Fighweel and you've got a live coding env for WebGL...