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 */