diff options
author | Sergey Sharybin <sergey.vfx@gmail.com> | 2016-05-20 15:16:54 +0300 |
---|---|---|
committer | Sergey Sharybin <sergey.vfx@gmail.com> | 2016-05-22 16:12:14 +0300 |
commit | 120492ace9150913f106f329942684c583a6f697 (patch) | |
tree | 117c2be4b67cfc5caa50b6a54f65ec3bf0387863 /source | |
parent | 6115267a845a49bdf9a5d4c701fcf5b995fc499a (diff) |
Cycles: Support bump mapping in GLSL viewport
This commit implements Bump node in GLSL, making it possible to
see previews of bump mapping in viewport without need to render.
Nothing really fancy going on here, just uses internal dFdx/dFdy
functions to get derivatives of the surface and map itself.
Quite basic but seems to behave correct-ish.
This commit also makes Displacement material output to affect
viewport shading by re-linking unconnected Normal input to a
node which was used for displacement output (via Bump node).
Intention of all this is to make it really easy to do bump map
painting with Cycles as an active render engine.
Reviewers: campbellbarton, mont29, brecht, psy-fi
Reviewed By: brecht
Subscribers: Blendify, eyecandy
Differential Revision: https://developer.blender.org/D2014
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/gpu/shaders/gpu_shader_material.glsl | 22 | ||||
-rw-r--r-- | source/blender/nodes/shader/node_shader_tree.c | 160 | ||||
-rw-r--r-- | source/blender/nodes/shader/nodes/node_shader_bump.c | 10 |
3 files changed, 188 insertions, 4 deletions
diff --git a/source/blender/gpu/shaders/gpu_shader_material.glsl b/source/blender/gpu/shaders/gpu_shader_material.glsl index dea5b994e74..28bf99b83dc 100644 --- a/source/blender/gpu/shaders/gpu_shader_material.glsl +++ b/source/blender/gpu/shaders/gpu_shader_material.glsl @@ -3160,9 +3160,27 @@ void node_normal_map(vec4 tangent, vec3 normal, vec3 texnormal, out vec3 outnorm outnormal = normalize(outnormal); } -void node_bump(float strength, float dist, float height, vec3 N, out vec3 result) +void node_bump(float strength, float dist, float height, vec3 N, vec3 surf_pos, out vec3 result) { - result = N; + vec3 dPdx = dFdx(surf_pos); + vec3 dPdy = dFdy(surf_pos); + + /* Get surface tangents from normal. */ + vec3 Rx = cross(dPdy, N); + vec3 Ry = cross(N, dPdx); + + /* Compute surface gradient and determinant. */ + float det = dot(dPdx, Rx); + float absdet = abs(det); + + float dHdx = dFdx(height); + float dHdy = dFdy(height); + vec3 surfgrad = dHdx*Rx + dHdy*Ry; + + strength = max(strength, 0.0); + + result = normalize(absdet*N - dist*sign(det)*surfgrad); + result = normalize(strength*result + (1.0 - strength)*N); } /* output */ diff --git a/source/blender/nodes/shader/node_shader_tree.c b/source/blender/nodes/shader/node_shader_tree.c index c4ec55c8d06..29b1e5bc5c7 100644 --- a/source/blender/nodes/shader/node_shader_tree.c +++ b/source/blender/nodes/shader/node_shader_tree.c @@ -199,12 +199,172 @@ void register_node_tree_type_sh(void) /* GPU material from shader nodes */ +/* Find an output node of the shader tree. + * + * NOTE: it will only return output which is NOT in the group, which isn't how + * render engines works but it's how the GPU shader compilation works. This we + * can change in the future and make it a generic function, but for now it stays + * private here. + */ +static bNode *ntree_shader_output_node(bNodeTree *ntree) +{ + /* Make sure we only have single node tagged as output. */ + ntreeSetOutput(ntree); + for (bNode *node = ntree->nodes.first; node != NULL; node = node->next) { + if (node->flag & NODE_DO_OUTPUT) { + return node; + } + } + return NULL; +} + +/* Find socket with a specified identifier. */ +static bNodeSocket *ntree_shader_node_find_socket(ListBase *sockets, + const char *identifier) +{ + for (bNodeSocket *sock = sockets->first; sock != NULL; sock = sock->next) { + if (STREQ(sock->identifier, identifier)) { + return sock; + } + } + return NULL; +} + +/* Find input socket with a specified identifier. */ +static bNodeSocket *ntree_shader_node_find_input(bNode *node, + const char *identifier) +{ + return ntree_shader_node_find_socket(&node->inputs, identifier); +} + +/* Find output socket with a specified identifier. */ +static bNodeSocket *ntree_shader_node_find_output(bNode *node, + const char *identifier) +{ + return ntree_shader_node_find_socket(&node->outputs, identifier); +} + +/* Check whether shader has a displacement. + * + * Will also return a node and it's socket which is connected to a displacement + * output. Additionally, link which is attached to the displacement output is + * also returned. + */ +static bool ntree_shader_has_displacement(bNodeTree *ntree, + bNode **r_node, + bNodeSocket **r_socket, + bNodeLink **r_link) +{ + bNode *output_node = ntree_shader_output_node(ntree); + if (output_node == NULL) { + /* We can't have displacement without output node, apparently. */ + return false; + } + /* Make sure sockets links pointers are correct. */ + ntreeUpdateTree(G.main, ntree); + bNodeSocket *displacement = ntree_shader_node_find_input(output_node, + "Displacement"); + + if (displacement == NULL) { + /* Non-cycles node is used as an output. */ + return false; + } + if (displacement->link != NULL) { + *r_node = displacement->link->fromnode; + *r_socket = displacement->link->fromsock; + *r_link = displacement->link; + } + return displacement->link != NULL; +} + +/* Use specified node and socket as an input for unconnected normal sockets. */ +static void ntree_shader_link_builtin_normal(bNodeTree *ntree, + bNode *node_from, + bNodeSocket *socket_from) +{ + for (bNode *node = ntree->nodes.first; node != NULL; node = node->next) { + if (node == node_from) { + /* Don't connect node itself! */ + continue; + } + bNodeSocket *sock = ntree_shader_node_find_input(node, "Normal"); + /* TODO(sergey): Can we do something smarter here than just a name-based + * matching? + */ + if (sock == NULL) { + /* There's no Normal input, nothing to link. */ + continue; + } + if (sock->link != NULL) { + /* Something is linked to the normal input already. can't + * use other input for that. + */ + continue; + } + /* Create connection between specified node and the normal input. */ + nodeAddLink(ntree, node_from, socket_from, node, sock); + } +} + +/* Re-link displacement output to unconnected normal sockets via bump node. + * This way material with have proper displacement in the viewport. + */ +static void ntree_shader_relink_displacement(bNodeTree *ntree, + short compatibility) +{ + if (compatibility != NODE_NEW_SHADING) { + /* We can only deal with new shading system here. */ + return; + } + bNode *displacement_node; + bNodeSocket *displacement_socket; + bNodeLink *displacement_link; + if (!ntree_shader_has_displacement(ntree, + &displacement_node, + &displacement_socket, + &displacement_link)) + { + /* There is no displacement output connected, nothing to re-link. */ + return; + } + /* We have to disconnect displacement output socket, otherwise we'll have + * cycles in the Cycles material :) + */ + nodeRemLink(ntree, displacement_link); + /* We can't connect displacement to normal directly, use bump node for that + * and hope that it gives good enough approximation. + */ + bNode *bump_node = nodeAddStaticNode(NULL, ntree, SH_NODE_BUMP); + bNodeSocket *bump_input_socket = ntree_shader_node_find_input(bump_node, "Height"); + bNodeSocket *bump_output_socket = ntree_shader_node_find_output(bump_node, "Normal"); + BLI_assert(bump_input_socket != NULL); + BLI_assert(bump_output_socket != NULL); + /* Connect bump node to where displacement output was originally + * connected to. + */ + nodeAddLink(ntree, + displacement_node, displacement_socket, + bump_node, bump_input_socket); + /* Connect all free-standing Normal inputs. */ + ntree_shader_link_builtin_normal(ntree, bump_node, bump_output_socket); + /* TODO(sergey): Reconnect Geometry Info->Normal sockets to the new + * bump node. + */ + /* We modified the tree, it needs to be updated now. */ + ntreeUpdateTree(G.main, ntree); +} + void ntreeGPUMaterialNodes(bNodeTree *ntree, GPUMaterial *mat, short compatibility) { /* localize tree to create links for reroute and mute */ bNodeTree *localtree = ntreeLocalize(ntree); bNodeTreeExec *exec; + /* Perform all needed modifications on the tree in order to support + * displacement/bump mapping. + */ + ntree_shader_relink_displacement(localtree, compatibility); + exec = ntreeShaderBeginExecTree(localtree); ntreeExecGPUNodes(exec, mat, 1, compatibility); ntreeShaderEndExecTree(exec); diff --git a/source/blender/nodes/shader/nodes/node_shader_bump.c b/source/blender/nodes/shader/nodes/node_shader_bump.c index 22027d58385..285dede71e6 100644 --- a/source/blender/nodes/shader/nodes/node_shader_bump.c +++ b/source/blender/nodes/shader/nodes/node_shader_bump.c @@ -51,8 +51,14 @@ static int gpu_shader_bump(GPUMaterial *mat, bNode *UNUSED(node), bNodeExecData in[3].link = GPU_builtin(GPU_VIEW_NORMAL); else GPU_link(mat, "direction_transform_m4v3", in[3].link, GPU_builtin(GPU_VIEW_MATRIX), &in[3].link); - - return GPU_stack_link(mat, "node_bump", in, out); + GPU_stack_link(mat, "node_bump", in, out, GPU_builtin(GPU_VIEW_POSITION)); + /* Other nodes are applying view matrix if the input Normal has a link. + * We don't want normal to have view matrix applied twice, so we cancel it here. + * + * TODO(sergey): This is an extra multiplication which cancels each other, + * better avoid this but that requires bigger refactor. + */ + return GPU_link(mat, "direction_transform_m4v3", out[0].link, GPU_builtin(GPU_INVERSE_VIEW_MATRIX), &out[0].link); } /* node type definition */ |